This recipe will show you how to implement a ribbon trail effect that is often used for emphasizing high-speed movements such as sword slashes, very fast vehicles passing by or, as you will see after finishing this recipe, the fastest running panda in the world.
Follow the instructions of Setting up the game structure found in Chapter 1 before you proceed to create a basic project setup.
The following steps are necessary for implementing the ribbon trail effect:
Ribbon.py
to the project and add the following code:from direct.task import Task from direct.task.TaskManagerGlobal import taskMgr from panda3d.core import * class RibbonNode(): def __init__(self, pos, damping): self.pos = Vec3(pos) self.damping = damping self.delta = Vec3() def update(self, pos): self.delta = (pos - self.pos) * self.damping self.pos += self.delta class Ribbon(): def __init__(self, parent, color, thickness, length, damping): self.parent = parent self.length = length self.thickness = thickness self.color = color self.lineGen = MeshDrawer() self.lineGen.setBudget(100) genNode = self.lineGen.getRoot() genNode.reparentTo(render) genNode.setTwoSided(True) genNode.setTransparency(True) pos = parent.getPos(render) self.trailPoints = [] for i in range(length): self.trailPoints.append(RibbonNode(pos, damping)) taskMgr.add(self.trail, "update trail") def getRoot(self): return self.lineGen.getRoot() def trail(self, task): pos = self.parent.getPos(render) self.trailPoints[0].update(pos) for i in range(1, self.length): ribbon trails effectimplementing, on objectself.trailPoints[i].update(self.trailPoints[i - 1].pos) self.lineGen.begin(base.cam, render) color = Vec4(self.color) thickness = self.thickness for i in range(self.length - 1): p1 = self.trailPoints[i].pos p2 = self.trailPoints[i + 1].pos startColor = Vec4(color) endColor = Vec4(color) endColor.setW(color.getW() - 0.2) color = Vec4(endColor) self.lineGen.unevenSegment(p1, p2, 0, thickness, startColor, thickness - 0.3, endColor) thickness -= 0.3 self.lineGen.end() return task.cont
Application.py
and enter the following lines of code:from direct.showbase.ShowBase import ShowBase from direct.showbase.RandomNumGen import RandomNumGen from direct.actor.Actor import Actor from panda3d.core import * from direct.interval.IntervalGlobal import * from Ribbon import Ribbon class Application(ShowBase): def __init__(self): ShowBase.__init__(self) self.panda = Actor("panda", {"walk": "panda-walk"}) self.panda.reparentTo(render) self.panda.loop("walk") self.panda.setHpr(-90, 0, 0) self.ribbon = Ribbon(self.panda, Vec4(1, 1, 1, 1), 3, 10, 0.3) ribbon trails effectimplementing, on objectself.ribbon.getRoot().setZ(5) self.walkIval1 = self.panda.posInterval(1, Vec3(-12, 0, 0), startPos = Vec3(12, 0, 0)) self.walkIval2 = self.panda.posInterval(1, Vec3(12, 0, 0), startPos = Vec3(-12, 0, 0)) self.turnIval1 = self.panda.hprInterval(0.1, Vec3(90, 0, 0), startHpr = Vec3(-90, 0, 0)) self.turnIval2 = self.panda.hprInterval(0.1, Vec3(-90, 0, 0), startHpr = Vec3(90, 0, 0)) self.pandaWalk = Sequence(self.walkIval1, self.turnIval1, self.walkIval2, self.turnIval2) self.pandaWalk.loop() self.cam.setPos(0, -60, 6) self.cam.lookAt(0, 0, 6)
Our code puts a trail behind our panda actor that slowly fades out. Let's take a closer look at the code that produced this effect.
In the constructor of the Ribbon class, after initializing our member variables, we set up a new MeshDrawer
, which is a very convenient class for working with dynamically updated geometry like our ribbons. We configure it to use a budget of 100 triangles and enable transparency and double sided rendering for the generated geometry.
After this is done, we fill a list of RibbonNodes
. Each of these nodes will then try to follow its predecessor in the list, but will be hampered by the amount of damping we specified in the constructor parameter, so our nodes are keeping some distance, between which we span some geometry using a MeshDrawer
and unevenSegment()
method that draws line segments with different sized ends. Not only the size of the line decreases, but we also make the alpha smaller and smaller with each segment until the trail smoothly fades out.
This leaves us with building a little test scene in our Application
class, connecting the ribbon to the panda that is moved back and forth using intervals.
The Ribbon
class is far from complete, but it does its job and shows nicely how the MeshDrawer
class can help to procedurally generate and modify geometry. Some points you may want to extend, for example, are the way the alpha value and the ribbon size are controlled.
Additionally, you could experiment with different damping values and behaviors. Instead of using the same damping value for all RibbonNode
objects in the trail, you could try to assign different values to the nodes, making the ones in the back of the trail slower, for example.