main.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. #!/usr/bin/env python
  2. # Author: Shao Zhang, Phil Saltzman, and Eddie Caanan
  3. # Last Updated: 2015-03-13
  4. #
  5. # This tutorial shows how to play animations on models aka "actors".
  6. # It is based on the popular game of "Rock 'em Sock 'em Robots".
  7. from direct.showbase.ShowBase import ShowBase
  8. from panda3d.core import AmbientLight, DirectionalLight
  9. from panda3d.core import TextNode
  10. from panda3d.core import LVector3
  11. from direct.gui.OnscreenText import OnscreenText
  12. from direct.interval.MetaInterval import Sequence
  13. from direct.interval.FunctionInterval import Func, Wait
  14. from direct.actor import Actor
  15. from random import random
  16. import sys
  17. class BoxingRobotDemo(ShowBase):
  18. # Macro-like function used to reduce the amount to code needed to create the
  19. # on screen instructions
  20. def genLabelText(self, text, i):
  21. return OnscreenText(text=text, parent=base.a2dTopLeft, scale=.05,
  22. pos=(0.1, - 0.1 -.07 * i), fg=(1, 1, 1, 1),
  23. align=TextNode.ALeft)
  24. def __init__(self):
  25. # Initialize the ShowBase class from which we inherit, which will
  26. # create a window and set up everything we need for rendering into it.
  27. ShowBase.__init__(self)
  28. # This code puts the standard title and instruction text on screen
  29. self.title = OnscreenText(text="Panda3D: Tutorial - Actors",
  30. parent=base.a2dBottomRight, style=1,
  31. fg=(0, 0, 0, 1), pos=(-0.2, 0.1),
  32. align=TextNode.ARight, scale=.09)
  33. self.escapeEventText = self.genLabelText("ESC: Quit", 0)
  34. self.akeyEventText = self.genLabelText("[A]: Robot 1 Left Punch", 1)
  35. self.skeyEventText = self.genLabelText("[S]: Robot 1 Right Punch", 2)
  36. self.kkeyEventText = self.genLabelText("[K]: Robot 2 Left Punch", 3)
  37. self.lkeyEventText = self.genLabelText("[L]: Robot 2 Right Punch", 4)
  38. # Set the camera in a fixed position
  39. self.disableMouse()
  40. camera.setPosHpr(14.5, -15.4, 14, 45, -14, 0)
  41. self.setBackgroundColor(0, 0, 0)
  42. # Add lighting so that the objects are not drawn flat
  43. self.setupLights()
  44. # Load the ring
  45. self.ring = loader.loadModel('models/ring')
  46. self.ring.reparentTo(render)
  47. # Models that use skeletal animation are known as Actors instead of models
  48. # Instead of just one file, the have one file for the main model, and an
  49. # additional file for each playable animation.
  50. # They are loaded using Actor.Actor instead of loader.LoadModel.
  51. # The constructor takes the location of the main object as with a normal model
  52. # and a dictionary (A fancy python structure that is like a lookup table)
  53. # that contains names for animations, and paths to the appropriate
  54. # files
  55. self.robot1 = Actor.Actor('models/robot',
  56. {'leftPunch': 'models/robot_left_punch',
  57. 'rightPunch': 'models/robot_right_punch',
  58. 'headUp': 'models/robot_head_up',
  59. 'headDown': 'models/robot_head_down'})
  60. # Actors need to be positioned and parented like normal objects
  61. self.robot1.setPosHprScale(-1, -2.5, 4, 45, 0, 0, 1.25, 1.25, 1.25)
  62. self.robot1.reparentTo(render)
  63. # We'll repeat the process for the second robot. The only thing that changes
  64. # here is the robot's color and position
  65. self.robot2 = Actor.Actor('models/robot',
  66. {'leftPunch': 'models/robot_left_punch',
  67. 'rightPunch': 'models/robot_right_punch',
  68. 'headUp': 'models/robot_head_up',
  69. 'headDown': 'models/robot_head_down'})
  70. # Set the properties of this robot
  71. self.robot2.setPosHprScale(1, 1.5, 4, 225, 0, 0, 1.25, 1.25, 1.25)
  72. self.robot2.setColor((.7, 0, 0, 1))
  73. self.robot2.reparentTo(render)
  74. # Now we define how the animated models will move. Animations are played
  75. # through special intervals. In this case we use actor intervals in a
  76. # sequence to play the part of the punch animation where the arm extends,
  77. # call a function to check if the punch landed, and then play the part of the
  78. # animation where the arm retracts
  79. # Punch sequence for robot 1's left arm
  80. self.robot1.punchLeft = Sequence(
  81. # Interval for the outstreched animation
  82. self.robot1.actorInterval('leftPunch', startFrame=1, endFrame=10),
  83. # Function to check if the punch was successful
  84. Func(self.checkPunch, 2),
  85. # Interval for the retract animation
  86. self.robot1.actorInterval('leftPunch', startFrame=11, endFrame=32))
  87. # Punch sequence for robot 1's right arm
  88. self.robot1.punchRight = Sequence(
  89. self.robot1.actorInterval('rightPunch', startFrame=1, endFrame=10),
  90. Func(self.checkPunch, 2),
  91. self.robot1.actorInterval('rightPunch', startFrame=11, endFrame=32))
  92. # Punch sequence for robot 2's left arm
  93. self.robot2.punchLeft = Sequence(
  94. self.robot2.actorInterval('leftPunch', startFrame=1, endFrame=10),
  95. Func(self.checkPunch, 1),
  96. self.robot2.actorInterval('leftPunch', startFrame=11, endFrame=32))
  97. # Punch sequence for robot 2's right arm
  98. self.robot2.punchRight = Sequence(
  99. self.robot2.actorInterval('rightPunch', startFrame=1, endFrame=10),
  100. Func(self.checkPunch, 1),
  101. self.robot2.actorInterval('rightPunch', startFrame=11, endFrame=32))
  102. # We use the same techinique to create a sequence for when a robot is knocked
  103. # out where the head pops up, waits a while, and then resets
  104. # Head animation for robot 1
  105. self.robot1.resetHead = Sequence(
  106. # Interval for the head going up. Since no start or end frames were given,
  107. # the entire animation is played.
  108. self.robot1.actorInterval('headUp'),
  109. Wait(1.5),
  110. # The head down animation was animated a little too quickly, so this will
  111. # play it at 75% of it's normal speed
  112. self.robot1.actorInterval('headDown', playRate=.75))
  113. # Head animation for robot 2
  114. self.robot2.resetHead = Sequence(
  115. self.robot2.actorInterval('headUp'),
  116. Wait(1.5),
  117. self.robot2.actorInterval('headDown', playRate=.75))
  118. # Now that we have defined the motion, we can define our key input.
  119. # Each fist is bound to a key. When a key is pressed, self.tryPunch checks to
  120. # make sure that the both robots have their heads down, and if they do it
  121. # plays the given interval
  122. self.accept('escape', sys.exit)
  123. self.accept('a', self.tryPunch, [self.robot1.punchLeft])
  124. self.accept('s', self.tryPunch, [self.robot1.punchRight])
  125. self.accept('k', self.tryPunch, [self.robot2.punchLeft])
  126. self.accept('l', self.tryPunch, [self.robot2.punchRight])
  127. # tryPunch will play the interval passed to it only if
  128. # neither robot has 'resetHead' playing (a head is up) AND
  129. # the punch interval passed to it is not already playing
  130. def tryPunch(self, interval):
  131. if (not self.robot1.resetHead.isPlaying() and
  132. not self.robot2.resetHead.isPlaying() and
  133. not interval.isPlaying()):
  134. interval.start()
  135. # checkPunch will determine if a successful punch has been thrown
  136. def checkPunch(self, robot):
  137. if robot == 1:
  138. # punch is directed to robot 1
  139. # if robot 1 is playing'resetHead', do nothing
  140. if self.robot1.resetHead.isPlaying():
  141. return
  142. # if robot 1 is not punching...
  143. if (not self.robot1.punchLeft.isPlaying() and
  144. not self.robot1.punchRight.isPlaying()):
  145. # ...15% chance of successful hit
  146. if random() > .85:
  147. self.robot1.resetHead.start()
  148. # Otherwise, only 5% chance of sucessful hit
  149. elif random() > .95:
  150. self.robot1.resetHead.start()
  151. else:
  152. # punch is directed to robot 2, same as above
  153. if self.robot2.resetHead.isPlaying():
  154. return
  155. if (not self.robot2.punchLeft.isPlaying() and
  156. not self.robot2.punchRight.isPlaying()):
  157. if random() > .85:
  158. self.robot2.resetHead.start()
  159. elif random() > .95:
  160. self.robot2.resetHead.start()
  161. # This function sets up the lighting
  162. def setupLights(self):
  163. ambientLight = AmbientLight("ambientLight")
  164. ambientLight.setColor((.8, .8, .75, 1))
  165. directionalLight = DirectionalLight("directionalLight")
  166. directionalLight.setDirection(LVector3(0, 0, -2.5))
  167. directionalLight.setColor((0.9, 0.8, 0.9, 1))
  168. render.setLight(render.attachNewNode(ambientLight))
  169. render.setLight(render.attachNewNode(directionalLight))
  170. demo = BoxingRobotDemo()
  171. demo.run()