Introduction
Basics
Functions
Compare?
Adding inputs
Variables
Adding outputs
Cleaning up
Renaming ports
Objects and link ports
Getting object parameters
Global variable
Setting object parameters
Getting the object to update
Introduction
New to version 8.1 of Cinema 4D is the COFFEE node, many of you will be scared
stiff by this little baby, but really there's no need to. Soon I'll have you
embracing that COFFEE node, and many of you may even take the next step and
move onto COFFEE tags or even full blown COFFEE plugins. It really isn't as
difficult as you might think, and in fact you'll soon find it an indispensable
part of your Xpresso armour.
Where to begin? Well, Lets start off with the basics and start by thinking
of the COFFEE node as simply a more powerful Function Node.
Basics
So the first thing to do is to examine this node and see how it works. Make
a new document and add a Null Object to your scene. Select the Null Object and
create a new Xpresso tag on that object RMB->New Expression->Xpresso Expression
In the Xpresso workspace add a new COFFEE node RMB->New Node->Xpresso->Calculate->COFFEE.
When the node is new it comes with two input ports (Input1 and Input2) and
one output port (Output1). Also if you select the node you will see in the Attributes
manager that it already contains a few lines of COFFEE code:
main()
{
Output1 = Input1 + Input2;
} |
|
So what do these lines mean? Well, "main()" and the curly brackets
merely tell Cinema that this is the piece of COFFEE code, a "Block"
that needs to be executed. The third line is all we are really interested in
at this moment in time "Outpu1=Input1+Input2;".
Most likely you've already worked out what this means anyway, but if not lets
examine it a bit closer.
The first piece we should look at is "Output1", you should notice
that this is in fact the name of the output port on the COFFEE node, in fact
if you want go ahead and rename the Output1 port to something you like.
You will find that you are only allowed to use Numbers and Letters, and you
mustn't use any spaces to name a COFFEE node port. This is because each port
is also a "Variable", and COFFEE isn't so friendly as to understand
"My Output", as far as COFFEE is concerned "My Output" is
in fact two variables"My" and "Output" and a space between
them will just make it throw out an error.
It's worth remembering that COFFEE is also case sensitive, that means it sees
upper case and lower case words differently, so "OUTPUT" is not the
same as "output" or as "Output". Which means that you could
have three outputs to the node called "OUTPUT","output"
and "Output" and they could each give you a completely different value.
This also means that you should always remember that when you're talking about
"OUTPUT" in a COFFEE program, it's not the same as "output".
Rename the port back to "Output1" as during the rest of this tutorial
this is the variable name we will be using for this port within COFFEE.
Getting back to the code, the next thing is a symbol "=", the equals
symbol. Equals on it's own in COFFEE is the same as writing "is equal to"
in English, so Output1=Intput1+Input2; becomes "Output1 is equal to Input1
plus Input2". The semicolon ";" at the end of the line is the
same as writing a full stop for COFFEE and almost every line of code should
end in a semicolon. Looking at it like this we can see this is pretty simple,
this node adds the two input values. We can test this simply by adding a Result
node RMB->New Node->Xpresso->General->Result, and connecting the
Output1 port to the input of the Result node. Then simply select the COFFEE
node and change the values of Input1 and Input2 in the Attributes manager on
the Parameter page, the Result node should display the result of the calculation.
Functions
Ok, so that's not very impressive, we already have a Math node that can do
this, well sure, but what if we wanted something a little more complicated?
lets change that third line of code. Make it read like the following:
main()
{
Output1=sqrt(Input1*Input1+Input2*Input2);
} |
|
Hey now we're outputting the length of a hypotenuse for a right angle triangle
who's shorter sides lengths are equal to Input1 and Input2. sqrt in COFFEE gives
us the Square Root of whatever is inside it's brackets.
Compare?
So now you're saying "Well, we already had the Function node, I still
don't see what's so great about this." well we can do more than math stuff
with our inputs, we can for instance compare things.
Replace the third line of code with the following:
main()
{
if (Input1==Input2)
{
Output1=TRUE;
}
else Output1=FALSE;
} |
|
Ok so now the node will only output 1 when both inputs are equal, and 0 at
all other times. How is this working? Simple, the "if" statement.
This works as follows, "if (something in here is true) { then do these
things} (else) otherwise {do these things instead}". So what's in the brackets
has to evaluate to be TRUE before the code in the curly brackets will do anything
in our case we're testing if Input1 is Equal to Input 2, "==" tells
COFFEE to test whether these two things are equal, if they are it returns true,
if not it returns false, other operators we could have used are "!="
isn't equal, ">" is greater than, "<" is less than,
>=" is greater than or equal to, "<=" is less than or equal
to.
At the end we have an "else" statement, when this is tagged onto
an "if" statement this gives us an opportunity to use some alternative
code instead for when what's in the if statements brackets does not evaluate
to be "true". In this case we execute some code that says Output1
will be false, FALSE is equal to 0 in COFFEE.
So you're thinking, well again we already have the Compare node, so what's
so great about this. Well, lets take this one step further, lets say we wanted
our node to only return TRUE if our input values were equal, and if when added
up they were less than 100. Lets change the first line of the if statement as
follows
main()
{
if (Input1==Input2 && Input1+Input2<100)
{
Output1=TRUE;
}
else Output1=FALSE;
} |
|
Now only when input 1 and input 2 added together are less than 100 and when
they're equal will the output be true. The "&&" means "and"
so to Cinema this reads "If input 1 is equal to input 2 and input1 plus
input 2 is less than 100 then.." the main other combination command you
can use is "||" which means "or".
Ok now we're getting somewhere and making something that you can't simply do
with one node normally, yet all we're written is three lines of code in a COFFEE
node.
Adding inputs
Well, how about if we wanted to have another variable or input to control the
value beneath which this node will output TRUE rater than being stuck with 100?
Simple, all we do is add a new input to the node, and change the code to incorporate
this new input. So click on the Blue square in the top left hand corner of the
COFFEE node and from the drop down list choose "Real" a new port will
be added called "Input3".
Now lets change the code to incorporate this new Input.
main()
{
if (Input1==Input2 && Input1+Input2<Input3)
{
Output1=TRUE;
}
else Output1=FALSE;
} |
|
Ok, so now our third input controls the maximum value that Input1 and Input2
added together can be to output a value to True when they're equal. But that's
not really logical, lets make it so that Input1 and Input2 can go up to the
value of Input3, so lets change this:
main()
{
if (Input1==Input2 && Input1<=Input3)
{
Output1=TRUE;
}
else Output1=FALSE;
} |
|
Ok that works, but why? Well simply put if this will only return TRUE if Input1
is lower than or equal to Input3, and it will also only return TRUE if Input1
and Input 2 are equal, then it follows that Input2 must also be less than or
equal to Input3 for this whole statement to be true.
Now something cool about COFFEE nodes is that you can add as many ports as
you need, COFFEE nodes allow you to create your own nodes. So lets say for instance
we wanted to add another Output that added up the number of times that Output1
returned true. How would we do this?
Variables
Firstly we would need to make a new variable, but one that's internal to the
COFFEE node, this will be our "counter", but we don't want it to be
reset every time the COFFEE node is evaluated otherwise it's never going to
be able to add up. The kind of variable we're talking about is what's known
as a "Global Variable", this is a variable that's declared outside
of the"scope" of any function.
How do we create or "declare" variables in COFFEE? We use the "var"
command. It works pretty simply in most cases, all we have to do is write "var
myvariable;" and then your variable is ready to get going with, you can
fill it with whatever and do whatever you want to it. In order to create a Global
Variable you have to write this command outside of any function, so our code
is going to now look like:
var Counter;
main()
{
if (Input1==Input2 && Input1<=Input3)
{
Output1=TRUE;
}
else Output1=FALSE;
} |
Here we declared (or created) our own global variable called "Counter",
because this is outside of any function it wont get reset unless the play head
is brought back to the beginning of the animation or you reset it yourself inside
of the COFFEE program. Declaring variables is pretty much all you can do outside
of any function in COFFEE, and you can't put anything into any variable that
you declare till it's inside of a function, this means that it wont have any
value in it at all at the moment, and COFFEE wont know what type of variable
it is we're dealing with, it could be a Link, or a Real, an Integer, or a Vector,
but COFFEE needs to know before it can really start to work with it. Before
we set it up it will only have the value of "nil" or "false"which
isn't much of anything really.
In order to set up the variable but not be setting it up every time the COFFEE
node is run (which would of course mean that Counter would never actually add
up or get beyond it's setting up value) we can simply test if Counter is false,
if so then we can give it some contents so that COFFEE will then be able to
deal with, from then on COFFEE will know that our variable contains data of
the sort that we put into it.
var Counter;
main()
{
if (!Counter)
Counter=0;
if (Input1==Input2 && Input1<=Input3)
{
Output1=TRUE;
}
else Output1=FALSE;
} |
The exclamation mark means "not" to COFFEE, so this line reads "if
Counter is not TRUE (i.e. it doesn't exist) then set counter to equal 0:"
Setting counter to equal 0 tells COFFEE that counter is a numerical value, so
it's either a Real or and Integer, and it's more likely to be an Integer.
Now that we set up the variable "Counter" it's time to actually make
it count, change the code as follows:
var Counter;
main()
{
if (!Counter) Counter=0;
if (Input1==Input2 && Input1<=Input3)
{
Counter=Counter+1;
Output1=TRUE;
}
else Output1=FALSE;
} |
So we added one new line "Counter=Counter+1;" all this does is add
one to the variable Counter each time we return TRUE. Now COFFEE actually allows
us several ways to write this, for instance we could also have written "Counter+=1;"
instead, and this would have done the same thing, or even "Counter++;".
As you get to know COFFEE and it's SDK you will learn that often there's more
than one way to do the same thing. This can make your life easier at times,
but it can also make your code difficult to read. Keep with what you find easiest
to understand for now as you want to make things as clear as possible for yourself
as you're writing code.
While we're on the subject of clarity, you can add remarks to your code two
ways in COFFEE, remarks will be ignored by Cinema, but just make life easier
for you, you can also sometimes use them to temporarily switch off lines of
code. The way to add a comment or remark is to add to forward slashes "//"
then everything beyond that on that line will be comments, and will be ignored
by Cinema. If you want to make a larger area of comments over several lines,
or want to "comment out" a larger block of code, then you add a forward
slash and an asterisk at the beginning of the comment "/*" and a asterisk
with a forward slash at the end of the comment "*/". Make sure to
add the "*/" at the end otherwise you may end up commenting out all
of your code and your program will do nothing or worse throw up an error.
Adding outputs
Back to the program, so now we've got "Counter" counting up, but
at the moment we can't see this, for all we know nothing is happening at all,
so let's make another output for the COFFEE node and add the code in our program
to send the value of Counter to our new output. So simply add a new output to
the COFFEE node by clicking on the red square in the top right hand corner of
the node, and from the drop down we want an "Integer" output, an Integer
is a whole number. Now we have a new port "Output2" we need to get
the value of Counter to that port, we do this quite simply by adding a new line
between lines 8 and 9:
var Counter;
main()
{
if (!Counter) Counter=0;
if (Input1==Input2 && Input1<=Input3)
{
Counter=Counter+1;
Output1=TRUE;
}
else Output1=FALSE; Output2=Counter;
} |
|
This assigns the value of Counter to the output Output2. Now if we wire up
a Result node, to the output Output2 we can see that every time Output1 is made
to be TRUE then Output2 counts up, but hang on there's a problem, if we move
the play head or do an action in the editor when Output1 is TRUE (1), then we
can see that Output2 is adding up, this isn't what we want, we wanted to count
the number of times that Output1 returned true, so we need to test if not only
our input parameters should return a TRUE result, but also if they were returning
a FALSE result before, that way we can add to Counter only at the time that
the state of Output1 actually changes to true.
In order to do this we should remember that the ports of a COFFEE node are
in fact Variables too, not only that, but they're Global variables, we can use
this to our advantage by testing whether Output1 was FALSE when our test of
the equality of Input1 and Input2 returns true, we will be able to add 1 to
Counter just at the time that the state of Output1 changes from FALSE to true,
so change the line "Counter=Counter+1;" to the following:
var Counter;
main()
{
if (!Counter) Counter=0;
if (Input1==Input2 && Input1<=Input3)
{
if (!Output1) Counter=Counter+1;
Output1=TRUE;
}
else Output1=FALSE; Output2=Counter;
} |
Now we're testing to see that Output1 is FALSE before adding one to Counter,
testing that it's FALSE here is fine because we know that we're just about to
set it to true, so that means we're catching it at the point where it's state
changes from FALSE to true.
When you hit play and Input1 and Input2 are equal and below Input3.Output2
will add one to it, and it will only add up the number of times that Output1
is TRUE (1).
Cleaning up
So now bearing in mind that all the ports are global variables, why should
we have to make the variable "Counter" internally, why not just use
the global variable of the port Output2? Answer, we don;t have to, we just did
that to show that you can, but we could for instance simplify the COFFEE listing
quite easily by replacing Counter with Output2, this would also mean we could
loose a couple of lines (the lines to be deleted are highlighted in red):
//var Counter;
main()
{
if (!Output2)
Output2=0;
if (Input1==Input2 && Input1<=Input3)
{
if (!Output1)
Output2=Output2+1;
Output1=TRUE;
}
else Output1=FALSE; //Output2=Counter;
} |
We're back down to only ten lines of code, it's important to try to keep your
COFFEE listings as simple and short as possible, and to prune out any dead wood,
as when you begin to work on longer listings or scenes with many COFFEE nodes
then this will speed things up considerably.
The trouble with our counter at the moment though is that it will just carry
on adding up pretty much forever, although it will reset when the the current
frame is rewound to the beginning of the animation. It would be much nicer if
we could simply have another input that if we put in the TRUE (1) we will reset
the counter.
First thing to do is to add another input, so add a new "Bool" input,
this can take either TRUE or FALSE as a value. Next we need to add the code
that will reset the counter "Output2", now we need to think about
where to put the line of code that will reset Output2, COFFEE executes the code
from the top line down, so if we put the code in too soon then even though the
Output will be reset if Input1 changes to match Input2 for instance at the same
time then Output2 will still be equal to 1 for one frame. So we need to put
the line of code after the counting actually happens, so here's how it should
go:
main()
{
if (!Output2) Output2=0;
if (Input1==Input2 && Input1<=Input3)
{
if (!Output1) Output2=Output2+1;
Output1=TRUE;
}
else Output1=FALSE;
if (Input4) Output2=0;
} |
|
Renaming ports
As you should see everything is working splendidly well, but the ports on the
node aren't very clearly labeled, and reading the code itself it's not exactly
easy to follow. Really we should have been naming the ports as we went along,
but seeing as the code is so small perhaps now would be a good time to rename
those ports.
Firstly, rename Input1 and Input2 as Value1 and Value2, then rename Input3
as Maximum, and Input4 as Reset, next rename Output1 as Result, and Output2
as Counter. Now, in the attributes manager code preview window you will notice
that Cinema has renamed all the variables accordingly in your program for you!
If you've been editing in the COFFEE editor, make sure to click the "Open
COFFEE Editor" button again so that what's in the node overwrites what's
in the COFFEE editor.
main()
{
if (!Counter)
Counter=0;
if (Value1==Value2
&& Value1<=Maximum)
{
if (!Result)
Counter=Counter+1;
Result =TRUE;
}
else Result=FALSE;
if (Reset) Counter=0;
} |
|
Objects and link ports
So, this is all very nice, and we have made a new useful node this way no doubt,
but we've only scratched the surface right now. What if we were to up and ante
and say, using the COFFEE node only lets make an object move up in the Y axis
by 10 units every time Value1 and Value2 are equal, not only that, but lets
say that we set the value of Maximum by the Y position of the object, then of
course we need for reset to still work, so now we're going to do something fairly
major.
Lets start off by getting a way to get object information into our COFFEE node,
for this we need to add a new input port of type "Link", so go ahead
and add this to our COFFEE node and rename it to "Object".
The "Object" port will allow us a direct link to pretty much all
information about any object that we then plug into it, however if we try to
get information out of the "Object" variable and nothing's connected
to our "Object" port, then we could end up with an error, while with
COFFEE nodes most errors wont cause a crash it's still good policy to try and
catch and possible situation where and error might occur and work a way around
it. In this case we can do this by testing if "Object" is FALSE (or
rather if it is "not true" if it's FALSE that means there's no object
there, and we can stop the COFFEE code by using the "return" statement,
this statement jumps out of the current function (in our case "main")
and returns the value that it precedes. For us using "return" in the
"main" function will cause the COFFEE node to just stop doing anything
more, and we don't actually need to put anything after "return" as
a value to return as all we're doing is breaking out.
main()
{
if (!Object) return;
if (!Counter) Counter=0;
if (Value1==Value2 && Value1<=Maximum)
{
if (!Result) Counter=Counter+1;
Result =TRUE;
}
else Result=FALSE;
if (Reset) Counter=0;
}
|
|
Now that we've protected ourselves against an error occurring if there's no
object actually connected we need to get some information about our object.
All objects in Cinema belong to a "class", objects of the type that
we're going to be connecting to our node like nulls, primitive objects, polygon
objects etc all belong to the BaseClass BaseObject, so if we look in the SDK
at the page for BaseObject we can see that there's a function in that class
for getting an object's position, it's "GetPosition()", if an object
is of a certain class that actually means that it contains all of the functions
of that class and that classes parent class, in order to access a function of
a class we use the arrow symbol, which is a combination of a minus "-"
and a greater than ">" to form an arrow "->" and we
use it in between our object and the name of the function so "myobject->function(function
parameters);" will call that function.
Getting object parameters
Looking at the SDK you can see that GetPosition() returns a "vector"
(on the left before the actual function it says vector), so that means we need
to have a variable to put that vector into, so lets add a line where we get
the objects position and put that into a new variable:
main()
{
if (!Object) return;
var objectPosition=Object->GetPosition();
if (!Counter) Counter=0;
if (Value1==Value2 && Value1<=Maximum)
{
if (!Result) Counter=Counter+1;
Result =TRUE;
}
else Result=FALSE;
if (Reset) Counter=0;
} |
Now we're only interested in the Y position of our object, and that's inside
of the "vector" object that we just retrieved and put into "objectPosition",
now if we look in the SDK at "vector" we will find that it contains
three real numbers stored in the variables x,y and z. In order to get a sub-variable
out of an object in COFFEE you use what's called "Dot Syntax", this
is to say, you write the name of your object, then you write a full stop (the
dot) and then you write the name of the variable, something like "myobject.variable".
We could simply plug the Y position back into objectPosition and COFFEE would
automatically redefine objectPosition as being a real, but for certain reasons
that will become apparent shortly it would be better if we just make a new variable
for the objects Y position to be stored in, we could also just use objectPosition.y
throughout rather than a new variable, but in this case doing things this way
means that in the tutorial we will cover more ground.
main()
{
if (!Object) return;
var objectPosition=Object->GetPosition();
var objectY=objectPosition.y;
if (!Counter) Counter=0;
if (Value1==Value2 && Value1<=Maximum)
{
if (!Result) Counter=Counter+1;
Result =TRUE;
}
else Result=FALSE;
if (Reset) Counter=0;
}
|
OK, so now we have the objects Y position, we wanted the value of Maximum to
be set by the Y position of our object, so maybe now would be a good time to
remove the input port "Maximum" and replace it in the code with our
variable "objectY" which contains the Y position of our object:
main()
{
if (!Object) return;
var objectPosition=Object->GetPosition();
var objectY=objectPosition.y;
if (!Counter) Counter=0;
if (Value1==Value2 && Value1<=objectY)
{
if (!Result) Counter=Counter+1;
Result =TRUE;
}
else Result=FALSE;
if (Reset) Counter=0;
} |
|
We now want every time Result is going to be TRUE instead of Counter adding
up, we're going to move our object up by 10. So all we have to do is replace
the variable "Counter" with the variable "objectY" on the
next line:
main()
{
if (!Object) return;
var objectPosition=Object->GetPosition();
var objectY=objectPosition.y;
if (!Counter) Counter=0;
if (Value1==Value2 && Value1<=objectY)
{
if (!Result) objectY=objectY+10;
Result =TRUE;
}
else Result=FALSE;
if (Reset) Counter=0;
} |
Global variable
Next we have to deal with resetting the Y position of our object when the Reset
port receives a TRUE value, we will have to do that by firstly storing the objects
y-position the first time the object is connected. To do this we will need a
new internal global variable, we'll call this "originalPosition",
and we'll set it up as soon as an object is plugged in. Then once reset is pressed,
we'll reset objectY to the value of originalPosition. This also now means that
we can simply dispense with the "Counter" output port, and all lines
that refer to that in the code.
var originalPosition;
main()
{
if (!Object) return;
var objectPosition=Object->GetPosition();
var objectY=objectPosition.y;
if (!originalPosition) originalPosition=objectY;
if (Value1==Value2 && Value1<=objectY)
{
if (!Result) objectY=objectY+10;
Result =TRUE;
}
else Result=FALSE;
if (Reset) objectY=originalPosition;
} |
|
Setting object parameters
So now, we've got our node adding 10 to the Y position of our object every
time that the value of Result is turned to TRUE. But hang on, there's nothing
actually happening to our object yet... why?
The reason is, all we've done is change a few variables, we haven't actually
changed any parameter of our object directly, remember in COFFEE we may actually
have several objects linked in that we're manipulating all with their own parameters,
maybe all of their details being put into an array or some other object, to
do this we will once again need to use the functions for a BaseObject, and you
will see that there's a function there called SetPosition(), this one just returns
a bool, that means if it's successful in doing what we want, then it returns
true, if it fails then it returns false, as with all functions we don't need
to have a variable to put the result into to call it, so next we're going to
add the line of code that calls this function after we've changed the value
of objectY
Looking at the SDK page again you will notice that in the brackets for the
function SetPosition it requires a function parameter to work, a vector, so
we need to know how to make a new vector to put in there, we could write a new
line of code that simply puts the new objectY position into the objectPosition.y
variable, but that would mean adding two new lines, so lets instead write it
all out on one line by using the "vector" function, this function
should be written as "vector(x,y,z);" and it returns as you may have
guessed a vector, so here's the new line:
var originalPosition;
main()
{
if (!Object) return;
var objectPosition=Object->GetPosition();
var objectY=objectPosition.y;
if (!originalPosition) originalPosition=objectY;
if (Value1==Value2 && Value1<=objectY)
{
if (!Result) objectY=objectY+10;
Result =TRUE;
}
else Result=FALSE;
if (Reset) objectY=originalPosition;
Object->SetPosition(vector(objectPosition.x,objectY,objectPosition.z));
} |
Getting the object to update
OK so we did everything the SDK told us to, yet the object still isn't moving.
Well there's one last thing we need to do, you see COFFEE allows us to set lots
of parameters for an object all at once, so you could on the new line write
"Object->SetRotation(vector(20,40,70)); and it would set the rotation
of the object as well, but you may not want COFFEE to actually tell the object
to update itself with the new information till you've set a few more parameters
or got some more data out of the object to affect other parameters. The other
thing to remember is that updating an object is actually fairly intensive, this
means you don't want the object to keep jumping around at each command as this
would slow things down, rather you want it all to happen once at the end of
your program.
COFFEE has a command to actually tell objects when (and sometimes what) to
update, and that command is the "Message" command, now, when you look
at the full list of commands alphabetically with the SDK you will find that
there are actually a whole heap of "Message" commands, and they're
all dependent on what kind of object we're dealing with. Well, if you were observant
you will have noticed that on the BaseObject page at the top it said "BaseList4D"
while there's no message command for BaseObject or Baselist4D there is one for
Baselist2D and Baselist2D is the parent class for Baselist4D (baselist4D being
the parent class for BaseObject), this actually means you can use all of these
functions from our Object, and we can use the BaseList2D::Message function.
Looking at it's page in the SDK was can see that it accepts an Integer (called
type) and possibly some data, now COFFEE has a whole load of preset Integer
variables, and looking in the description pane for Message you will see it actually
lists them under Message, and has an explanation for each Variable, these variables
as known a constant variables, that is you can't change them, and you'd better
not try to make your own variables with the same names otherwise you're going
to end up with an error, so we want to just tell the object to generally update,
and the variable MSG_UPDATE is all that we need to do this, so this is how it
should go:
var originalPosition;
main()
{
if (!Object) return;
var objectPosition=Object->GetPosition();
var objectY=objectPosition.y;
if (!originalPosition) originalPosition=objectY;
if (Value1==Value2 && Value1<=objectY)
{
if (!Result) objectY=objectY+10;
Result =TRUE;
}
else Result=FALSE;
if (Reset) objectY=originalPosition;
Object->SetPosition(vector(objectPosition.x,objectY,objectPosition.z));
Object->Message(MSG_UPDATE);
} |
So there you go, now we've manipulated an object using COFFEE and the COFFEE
node in Xpresso and just a few lines of code. Now remember when the play head
is rewound all the global variables will be reset, this means that we will actually
loose the objects original Y position, so a nice project for you is to either
by using Xpresso, or by using the COFFEE node and adding a couple of new ports
make it so that on the last frame of the animation the objects Y position is
reset.
As a second project, try making a COFFEE node that changes the child of the
object being inputted to the node in some way (Hint, check out BaseList4D).
Happy coding. |