HoudiniPython: Making a realtime game out of nodes
Updated: Apr 16, 2020
If you've read my previous HoudiniPython post, you'll know combining Python with Houdini can result in some powerful stuff.
In the following example, I have made 'Gravity Pong' out of nodes.
One node acts as a joystick. Moving it to the blue area will make the bat move left. Opposite with the red area. The goal? Keep the ball bouncing.
How is this possible?
Let's start off with the basics.
For those familiar with game programming, this might be an easy read for you.
We will need to fire up Houdini and place down a Python node in a new geo.
Let's look at
Variables allow you to store information between frames. In the case of my Pong game, I'm storing the velocity of the ball so I know how fast it's travelling.
With Houdini, we can store data on a node with Parameters.
To add a parameter, go Edit Parameter Interface and add a Float.
Give it a name and a label. I chose to do health.
Something to note: The name is what we will reference it by in the code. The label is what us Houdini users see on the interface.
Alright, so Player Health is currently at 0.
How do we set it to 100?
In Python, we can use:
You'll see that straight away the Player Health will set to 100. This is what happens when the Python node 'cooks'. It does the action you give it straight away.
This means we can't exactly use variables in Python because the contents will be wiped everytime we recook the node.
Let's try decrementing the health.
To do that, we will need to know what health was beforehand.
We can use:
So decrementing just means we have to minus one.
currentHealth = node.parm('health').eval() node.parm('health').set(currentHealth - 1)
When you enter the code, the Player Health will go to 99.
How do I make it run that again? One way is to put a space in the code and click elsewhere.
We need a way to tell the node to
Put the following line at the top of your code:
frame = hou.frame()
That's going to force the node to calculate the frame.
This means everytime we go to another frame, it will recalculate the frame and also execute the entire code again. You will notice a little clock icon appear. That tells you it'll cook every frame.
Now we just set the timeline to last for a huge number of frames (e.g. 24000000) and hit the play button.
You will notice we can't see the Player Health lowering. As soon as you hit the stop button, the parameter will update its display and you will see the Player Health update at a low number like -440. While we can't see the numbers updating, it's computing in the background.
Make our nodes move!
Adjusting node position is a bit easier since there's already a parameter we can get and set.
node.position() returns an array with the x and y coordinates.
node.setPosition() requires and array.
When we hit play, this code will make our node rise:
frame = hou.frame() node = hou.pwd() nodePos = node.position() nodePos += 0.01 node.setPosition(nodePos)
Ah bummer, our node has moved too far away.
Fear not, we can get it back.
Since we have a 'frame' variable, we can place it back at the start on the first few frames.
if frame < 10: node.setPosition([0,0])
When you hit the First Frame button, the node should be back home.
While we're on the topic, you can import random to make your nodes dance around.
Try this on another node:
import random as r frame = hou.frame() node = hou.pwd() node.setPosition([r.random()*2, r.random()*2])
You'll notice the other node stops doing its thing.
To make both of them update, link them to a merge node and display the merge instead.
It also works if you link python nodes are connected to eachother.
This is how we can have multiple nodes running at once. Just like how the ball, the bat, the joystick, and the game nodes all run in harmony.
To calculate collisions, we will need one node to know the position of another node.
We can use hou.node('../') to store a reference to the other node.
frame = hou.frame() node = hou.pwd() nodePos = node.position() nodePos += 0.002 node.setPosition(nodePos) otherNode = hou.node('../python3') otherNodePos = otherNode.position() if abs(nodePos - otherNodePos) < 0.5: print "ouch" node.setPosition([0,0])
If your nodes are circular, you can compare the distance between them. If they're rectangular you can do full AABB collision detection.
Since we have references to other nodes, we can also use their parameters.
if otherNode.parm('health') > 90: print "damn that other node is healthy"
We can also modify the parameters of other nodes.
In my Pong game, I hand a main game node. In the first few frames, it will ensure all other nodes have a parameter 'isRunning' set to 0. When the game started, it'll switch all of those parameters to 1. The nodes themselves would be checking to see if their isRunning parameter was set to 1. If so, they'd run their logic. Otherwise, they'd be doing their initialisation stuff (going to the starting place, setting their parameters etc..).
I like to use a bunch of helper functions and methods to make my code cleaner.
For example, we can turn this:
if (node.parm('score').eval() > node.parm('highscore').eval()): node.parm('highscore').set(node.parm('score').eval())
????? What does this mean ?????
def set_highscore(newHighscore): node.parm('highscore').set(newHighscore) def get_highscore(): return node.parm('highscore').eval() def get_score(): return node.parm('score').eval() if (get_score() > get_highscore()): set_highscore(get_score())
Ah, much better.
Another thing, what about a
Well, our counter is currently just a sticky note with a hidden background.
To see the name of this note, simply drag it into a Python Shell window.
We can then use setText() to set its contents.
scoreDisplay = hou.item('../__stickynote1') scoreDisplay.setText("5")
Using previous logic, we can also make our text count up.
Is there any
I've got a system that seems to work fine for me.
Every frame follows the same cycle:
getData() retreives all the information from the parameters and loads them into global variables that are accessible all over the code. This also includes reading parameters from other nodes.
update() is where the magic happens. That's where you update the variables based on the changing system. For my Pong game, the ball would update its position variable based on the velocity, and update the velocity based on the gravity.
display() is where we show the user what has changed. This includes changing the position of a node, changing text on sticky notes, (and for the pros) adding/deleting nodes.
storeData() is where we put the updated variables back into our parameters. Safe and ready to be retreived come the next frame.
All python nodes in my scene operate with the same logic.
Here's an experiment I made.
The cake is a lie.