toontown-just-works/build/nirai/panda3d/samples/boxing-robots/main.py

200 lines
9.1 KiB
Python
Raw Normal View History

2024-07-07 18:08:39 -05:00
#!/usr/bin/env python
# Author: Shao Zhang, Phil Saltzman, and Eddie Caanan
# Last Updated: 2015-03-13
#
# This tutorial shows how to play animations on models aka "actors".
# It is based on the popular game of "Rock 'em Sock 'em Robots".
from direct.showbase.ShowBase import ShowBase
from panda3d.core import AmbientLight, DirectionalLight
from panda3d.core import TextNode
from panda3d.core import LVector3
from direct.gui.OnscreenText import OnscreenText
from direct.interval.MetaInterval import Sequence
from direct.interval.FunctionInterval import Func, Wait
from direct.actor import Actor
from random import random
import sys
class BoxingRobotDemo(ShowBase):
# Macro-like function used to reduce the amount to code needed to create the
# on screen instructions
def genLabelText(self, text, i):
return OnscreenText(text=text, parent=base.a2dTopLeft, scale=.05,
pos=(0.1, - 0.1 -.07 * i), fg=(1, 1, 1, 1),
align=TextNode.ALeft)
def __init__(self):
# Initialize the ShowBase class from which we inherit, which will
# create a window and set up everything we need for rendering into it.
ShowBase.__init__(self)
# This code puts the standard title and instruction text on screen
self.title = OnscreenText(text="Panda3D: Tutorial - Actors",
parent=base.a2dBottomRight, style=1,
fg=(0, 0, 0, 1), pos=(-0.2, 0.1),
align=TextNode.ARight, scale=.09)
self.escapeEventText = self.genLabelText("ESC: Quit", 0)
self.akeyEventText = self.genLabelText("[A]: Robot 1 Left Punch", 1)
self.skeyEventText = self.genLabelText("[S]: Robot 1 Right Punch", 2)
self.kkeyEventText = self.genLabelText("[K]: Robot 2 Left Punch", 3)
self.lkeyEventText = self.genLabelText("[L]: Robot 2 Right Punch", 4)
# Set the camera in a fixed position
self.disableMouse()
camera.setPosHpr(14.5, -15.4, 14, 45, -14, 0)
self.setBackgroundColor(0, 0, 0)
# Add lighting so that the objects are not drawn flat
self.setupLights()
# Load the ring
self.ring = loader.loadModel('models/ring')
self.ring.reparentTo(render)
# Models that use skeletal animation are known as Actors instead of models
# Instead of just one file, the have one file for the main model, and an
# additional file for each playable animation.
# They are loaded using Actor.Actor instead of loader.LoadModel.
# The constructor takes the location of the main object as with a normal model
# and a dictionary (A fancy python structure that is like a lookup table)
# that contains names for animations, and paths to the appropriate
# files
self.robot1 = Actor.Actor('models/robot',
{'leftPunch': 'models/robot_left_punch',
'rightPunch': 'models/robot_right_punch',
'headUp': 'models/robot_head_up',
'headDown': 'models/robot_head_down'})
# Actors need to be positioned and parented like normal objects
self.robot1.setPosHprScale(-1, -2.5, 4, 45, 0, 0, 1.25, 1.25, 1.25)
self.robot1.reparentTo(render)
# We'll repeat the process for the second robot. The only thing that changes
# here is the robot's color and position
self.robot2 = Actor.Actor('models/robot',
{'leftPunch': 'models/robot_left_punch',
'rightPunch': 'models/robot_right_punch',
'headUp': 'models/robot_head_up',
'headDown': 'models/robot_head_down'})
# Set the properties of this robot
self.robot2.setPosHprScale(1, 1.5, 4, 225, 0, 0, 1.25, 1.25, 1.25)
self.robot2.setColor((.7, 0, 0, 1))
self.robot2.reparentTo(render)
# Now we define how the animated models will move. Animations are played
# through special intervals. In this case we use actor intervals in a
# sequence to play the part of the punch animation where the arm extends,
# call a function to check if the punch landed, and then play the part of the
# animation where the arm retracts
# Punch sequence for robot 1's left arm
self.robot1.punchLeft = Sequence(
# Interval for the outstreched animation
self.robot1.actorInterval('leftPunch', startFrame=1, endFrame=10),
# Function to check if the punch was successful
Func(self.checkPunch, 2),
# Interval for the retract animation
self.robot1.actorInterval('leftPunch', startFrame=11, endFrame=32))
# Punch sequence for robot 1's right arm
self.robot1.punchRight = Sequence(
self.robot1.actorInterval('rightPunch', startFrame=1, endFrame=10),
Func(self.checkPunch, 2),
self.robot1.actorInterval('rightPunch', startFrame=11, endFrame=32))
# Punch sequence for robot 2's left arm
self.robot2.punchLeft = Sequence(
self.robot2.actorInterval('leftPunch', startFrame=1, endFrame=10),
Func(self.checkPunch, 1),
self.robot2.actorInterval('leftPunch', startFrame=11, endFrame=32))
# Punch sequence for robot 2's right arm
self.robot2.punchRight = Sequence(
self.robot2.actorInterval('rightPunch', startFrame=1, endFrame=10),
Func(self.checkPunch, 1),
self.robot2.actorInterval('rightPunch', startFrame=11, endFrame=32))
# We use the same techinique to create a sequence for when a robot is knocked
# out where the head pops up, waits a while, and then resets
# Head animation for robot 1
self.robot1.resetHead = Sequence(
# Interval for the head going up. Since no start or end frames were given,
# the entire animation is played.
self.robot1.actorInterval('headUp'),
Wait(1.5),
# The head down animation was animated a little too quickly, so this will
# play it at 75% of it's normal speed
self.robot1.actorInterval('headDown', playRate=.75))
# Head animation for robot 2
self.robot2.resetHead = Sequence(
self.robot2.actorInterval('headUp'),
Wait(1.5),
self.robot2.actorInterval('headDown', playRate=.75))
# Now that we have defined the motion, we can define our key input.
# Each fist is bound to a key. When a key is pressed, self.tryPunch checks to
# make sure that the both robots have their heads down, and if they do it
# plays the given interval
self.accept('escape', sys.exit)
self.accept('a', self.tryPunch, [self.robot1.punchLeft])
self.accept('s', self.tryPunch, [self.robot1.punchRight])
self.accept('k', self.tryPunch, [self.robot2.punchLeft])
self.accept('l', self.tryPunch, [self.robot2.punchRight])
# tryPunch will play the interval passed to it only if
# neither robot has 'resetHead' playing (a head is up) AND
# the punch interval passed to it is not already playing
def tryPunch(self, interval):
if (not self.robot1.resetHead.isPlaying() and
not self.robot2.resetHead.isPlaying() and
not interval.isPlaying()):
interval.start()
# checkPunch will determine if a successful punch has been thrown
def checkPunch(self, robot):
if robot == 1:
# punch is directed to robot 1
# if robot 1 is playing'resetHead', do nothing
if self.robot1.resetHead.isPlaying():
return
# if robot 1 is not punching...
if (not self.robot1.punchLeft.isPlaying() and
not self.robot1.punchRight.isPlaying()):
# ...15% chance of successful hit
if random() > .85:
self.robot1.resetHead.start()
# Otherwise, only 5% chance of sucessful hit
elif random() > .95:
self.robot1.resetHead.start()
else:
# punch is directed to robot 2, same as above
if self.robot2.resetHead.isPlaying():
return
if (not self.robot2.punchLeft.isPlaying() and
not self.robot2.punchRight.isPlaying()):
if random() > .85:
self.robot2.resetHead.start()
elif random() > .95:
self.robot2.resetHead.start()
# This function sets up the lighting
def setupLights(self):
ambientLight = AmbientLight("ambientLight")
ambientLight.setColor((.8, .8, .75, 1))
directionalLight = DirectionalLight("directionalLight")
directionalLight.setDirection(LVector3(0, 0, -2.5))
directionalLight.setColor((0.9, 0.8, 0.9, 1))
render.setLight(render.attachNewNode(ambientLight))
render.setLight(render.attachNewNode(directionalLight))
demo = BoxingRobotDemo()
demo.run()