Skip to content

Instantly share code, notes, and snippets.

@kergalym
Created January 4, 2022 00:42
Show Gist options
  • Save kergalym/f9bef48e950d2e046620d13e1ef636f8 to your computer and use it in GitHub Desktop.
Save kergalym/f9bef48e950d2e046620d13e1ef636f8 to your computer and use it in GitHub Desktop.
bow_shoot_test for panda3d
from pathlib import Path
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.OnscreenText import OnscreenText
from direct.interval.FunctionInterval import Func
from direct.interval.IntervalGlobal import Sequence, Wait, Parallel, ProjectileInterval
from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor
from direct.task.TaskManagerGlobal import taskMgr
from panda3d.bullet import BulletSoftBodyNode, BulletCapsuleShape, BulletRigidBodyNode, ZUp, \
BulletCharacterControllerNode, BulletDebugNode, BulletBoxShape, BulletPlaneShape, BulletSphereShape, \
BulletGhostNode, BulletSoftBodyConfig, BulletHelper
from panda3d.core import Vec4, load_prc_file_data, Vec3, LODNode, NodePath, Texture, Material, PointLight, Point3, \
BitMask32, GeomNode, Vec2, LVector3, WindowProperties, TextureStage, TexGenAttrib, CollisionTraverser, CollisionRay, \
CollisionNode, CollideMask, CollisionHandlerQueue, TextNode, TransparencyAttrib
from Engine.Render.rpcore.render_pipeline import RenderPipeline
from Engine.Render.rpcore.util.movement_controller import MovementController
from panda3d.bullet import BulletWorld
from Engine.Render.rpcore import PointLight
import json
""" Test Scene with game mechanics: walking/turning in place with animation blending in Player mode,
mounting horse in AI mode, cloth simulation, TPS mouse camera with smooth reaction to obstacles
"""
load_prc_file_data(
'',
'win-size 1920 1080'
)
load_prc_file_data(
'',
'icon-filename icon-16.ico\n'
'win-origin -1 -2\n'
'window-title Panda3D + RP Test Scene with game mechanic \n'
'show-frame-rate-meter t\n'
'audio-library-name p3openal_audio\n'
'model-cache-dir Cache\n'
'model-cache-textures t\n'
'compressed-textures 0\n'
'bullet-filter-algorithm groups-mask\n'
'hardware-animated-vertices t\n'
'basic-shaders-only f\n'
'texture-compression f\n'
'driver-compress-textures f\n'
'task-timer-verbose 0\n'
'pstats-tasks 1\n'
)
class Application(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.disable_mouse()
self.start = False
self.start_riding = False
self.start_cam = False
self.start_pickup = False
self.spine_bone = None
self.pin = None
self.cloak = None
self.korlan_bs = None
self.is_playing = None
# Initialize renderer
self.game_dir = str(Path.cwd())
self.render_pipeline = RenderPipeline()
self.render_pipeline.pre_showbase_init()
self.render_pipeline.create(self)
# SETTING LIGHT
self.light = PointLight()
self.light.pos = (0, 0, 5)
self.light.color = (0.784314, 0.388235, 0.215686)
self.light.energy = 20.0
self.light.ies_profile = self.render_pipeline.load_ies_profile("x_arrow.ies")
self.light.casts_shadows = True
self.light.shadow_map_resolution = 512
self.light.near_plane = 0.2
self.render_pipeline.add_light(self.light)
# Setup for LEVEL OF DETAILS
self.lod = LODNode('SingleLOD')
self.lod_np = NodePath(self.lod)
self.lod_np.reparentTo(render)
# WORLD
self.env = self.loader.load_model("{0}/tmp/samples/bump-mapping/models/abstractroom2.egg".format(self.game_dir))
for child in self.env.getChildren():
self.lod.addSwitch(500.0, 0.0)
child.reparent_to(self.lod_np)
child.hide()
self.env.set_scale(1)
self.env.hide()
# PLAYER
self.korlan = Actor("{0}/Assets/Actors/Korlan/Korlan_no_cloak.egg.bam".format(self.game_dir),
{"idle": '{0}/Assets/Animations/Korlan-Standing_idle_female.egg'.format(self.game_dir),
"walk": "{0}/Assets/Animations/Korlan-Walking.egg".format(self.game_dir),
"left_t": "{0}/Assets/Animations/exp/Korlan-left_turn.egg".format(self.game_dir),
"right_t": "{0}/Assets/Animations/exp/Korlan-right_turn.egg".format(self.game_dir),
"mount": "{0}/Assets/Animations/Korlan-horse_mounting.egg".format(self.game_dir),
"horse_riding": "{0}/Assets/Animations/Korlan-horse_riding_idle".format(self.game_dir),
"unmount": "{0}/Assets/Animations/Korlan-horse_unmounting.egg".format(self.game_dir),
"slash": "{0}/Assets/Animations/Korlan-great_sword_slash.egg".format(self.game_dir),
"draw_bow": "{0}/Assets/Animations/Korlan-archer_standing_draw_arrow.egg".format(
self.game_dir),
}
)
self.cloak = base.loader.loadModel('{0}/Assets/Actors/Korlan/cloak.bam'.format(self.game_dir))
self.oldpos = self.korlan.get_pos()
# Set two sided, since some model may be broken
self.korlan.set_two_sided(True)
# Panda3D 1.10 doesn't enable alpha blending for textures by default
self.korlan.set_transparency(True)
# Add to LOD Node
self.lod.addSwitch(50.0, 0.0)
self.korlan.reparent_to(self.lod_np)
# Sword & Bow
hips_joint = self.korlan.exposeJoint(None, "modelRoot", "Korlan:Spine1")
self.sword = base.loader.loadModel('{0}/Assets/Weapons/sword.egg'.format(self.game_dir))
self.sword.set_name("sword")
self.sword.reparent_to(hips_joint)
self.bow = base.loader.loadModel('{0}/Assets/Weapons/bow.egg'.format(self.game_dir))
self.bow.set_name("bow")
self.bow.reparent_to(hips_joint)
self.arrow = base.loader.loadModel('{0}/Assets/Weapons/bow_arrow.egg'.format(self.game_dir))
self.arrow.set_name("bow_arrow")
self.arrow.reparent_to(hips_joint)
self.arrow.set_python_tag("power", 0)
self.arrow.set_python_tag("ready", 0)
self.arrow.set_python_tag("shot", 0)
self.draw_bow_is_done = 0
# positioning and scaling
self.sword.set_pos(10, 20, -8)
self.sword.set_hpr(325.30, 343.30, 7.13)
self.sword.set_scale(100)
self.bow.set_pos(0, 12, -12)
self.bow.set_hpr(78.69, 99.46, 108.43)
self.bow.set_scale(100)
self.arrow.set_pos(-10, 7, -12)
self.arrow.set_hpr(91.55, 0, 0)
self.arrow.set_scale(100)
self.has_weapon = {"sword": False, "bow": False}
# HORSE
self.horse = Actor("{0}/Assets/Actors/NPC/NPC_Animals/Horse/NPC_Horse_rp.egg".format(self.game_dir),
{"idle": "{0}/Assets/Animations/Animals/Horse/horse-horse_idle.egg".format(self.game_dir)})
self.lod.addSwitch(50.0, 0.0)
self.horse.reparent_to(self.lod_np)
self.horse.set_x(self.korlan.get_x() + 1)
self.horse.set_y(self.korlan.get_y() + 1)
self.horse.set_z(self.korlan.get_z())
self.horse.loop("idle")
# SETTING SRBG [SUCCESS]
for tex in render.findAllTextures():
if tex.getNumComponents() == 4:
tex.setFormat(Texture.F_srgb_alpha)
elif tex.getNumComponents() == 3:
tex.setFormat(Texture.F_srgb)
# Events: 1 is looping anim, 0 is playing (once) anim
base.accept("f1", self.toggle_physics_debug)
self.accept("k", self.start_play)
self.accept("e", self.interact, [self.horse, self.korlan, 'spine.003'])
self.accept("b", self.get_weapon, [self.korlan, "sword", 'Korlan:LeftHand'])
self.accept("m", self.get_weapon, [self.korlan, "bow", 'Korlan:LeftHand'])
self.accept("n", self.get_weapon, [self.korlan, "hands", 'Korlan:Spine1'])
self.accept("z", self.drop_item, [self.korlan])
self.accept("attach_floater_to_player", self.attach_floater, [self.korlan_bs])
self.accept("attach_floater_to_horse", self.attach_floater, [self.horse])
self.accept("idle", self.anim_state, [self.korlan, "idle", 1])
self.accept("walk", self.anim_state, [self.korlan, "walk", 1])
self.accept("left_t", self.anim_state, [self.korlan, "left_t", 1])
self.accept("right_t", self.anim_state, [self.korlan, "right_t", 1])
self.accept("horse_riding", self.anim_state, [self.korlan, "horse_riding", 1])
self.accept("mount", self.mount_seq, [self.horse, self.korlan, 'spine.003'])
self.accept("slash", self.anim_state, [self.korlan, "slash", 0])
self.accept("draw_bow", self.draw_bow_anim_state, [self.korlan, "draw_bow", 0])
self.accept("l", self.bow_shoot)
self.accept("p", self.shootBullet)
# self.accept("u", self.unmount_seq, [self.horse, self.korlan, 'spine.003'])
self.accept("a", self.setKey, ["left", True])
self.accept("d", self.setKey, ["right", True])
self.accept("w", self.setKey, ["forward", True])
self.accept("s", self.setKey, ["backward", True])
self.accept("a-up", self.setKey, ["left", False])
self.accept("d-up", self.setKey, ["right", False])
self.accept("w-up", self.setKey, ["forward", False])
self.accept("s-up", self.setKey, ["backward", False])
self.accept("mouse1", self.setKey, ["melee_attack", True])
self.accept("mouse3", self.setKey, ["archery_attack", True])
self.accept("mouse1-up", self.setKey, ["melee_attack", False])
self.accept("mouse3-up", self.setKey, ["archery_attack", False])
# Speed Parameters
self.attackDistance = 0.75
self.velocity = Vec3(0, 0, 0)
self.acceleration = 1.0
# Facing by X vector
self.xVector = Vec2(1, 0)
# Mounting State
self.states = {"is_ready_to_be_used": False,
"mount": False,
"is_in_mounting": False,
"is_in_shooting": False}
self.parent_joint = None
self.stateControl = None
self.is_mount_seq_done = False
self.is_unmount_seq_done = False
self.seq = None
self.mount_place_pos = Vec3(0, 0, 0)
self.mount_place_hpr = Vec3(0, 0, 0)
self.debug = False
# TPS Camera variables
self.floater = None
self.cam_area = None
self.pivot = None
self.cam_y_back_pos = -4.5
self.mouse_sens = 0.2
self.cam_is_close_to_obstacles = False
self.cam_is_not_close_to_player = False
self.is_got_pitch_edge = False
self.wp = None
self.key_map = {
'forward': False,
'backward': False,
'left': False,
'right': False,
'melee_attack': False,
'archery_attack': False}
self.speed = 1.0
# Cloth Sim and Physics
self.world = None
self.cloth_world = None
self.debug_nodepath = None
self.trigger_np = None
self.trigger = None
self.npcs_cont = {}
self.raytest_result = None
self.trajectory = None
self.hit_target = None
self.target_pos = None
self.target_test_ui = OnscreenText(text="",
pos=(-1.8, 0.8),
scale=0.03,
fg=(255, 255, 255, 0.9),
align=TextNode.ALeft,
mayChange=True)
self.cursor_ui = OnscreenImage(image="{0}/Settings/UI/ui_tex/hud/crosshair.png".format(self.game_dir),
pos=(0, 0, 0), scale=0.04)
self.cursor_ui.setTransparency(TransparencyAttrib.MAlpha)
self.cursor_ui.hide()
self.is_aiming = False
# Tasks
self.world_setup()
taskMgr.add(self.update_physics_task, 'update world')
taskMgr.add(self.start_journey_task, "get horse")
self.set_floater()
self.attach_floater(self.korlan_bs)
taskMgr.add(self.start_tp_cam_task, "start tp cam", extraArgs=[self.korlan], appendTask=True)
taskMgr.add(self.player_state_task, "movement_state_task",
extraArgs=[self.korlan, self.horse], appendTask=True)
taskMgr.add(self.update_cam_trigger_once_task, "update_cam_trigger_once_task")
taskMgr.add(self.cursor_state_task, "cursor_state_task")
taskMgr.add(self.arrow_hit_check, "arrow_hit_check")
taskMgr.add(self.charge_arrow_task, "charge_arrow_task")
taskMgr.add(self.arrow_fly_task, "arrow_fly_task")
# render.explore()
self.bullets = []
def removeBullet(self, task):
if len(self.bullets) < 1: return
bulletNP = self.bullets.pop(0)
self.world.removeRigidBody(bulletNP.node())
return task.done
def shootBullet(self):
# Get from/to points from mouse click
pMouse = base.mouseWatcherNode.getMouse()
pFrom = Point3()
pTo = Point3()
base.camLens.extrude(pMouse, pFrom, pTo)
pFrom = render.getRelativePoint(base.cam, pFrom)
pTo = render.getRelativePoint(base.cam, pTo)
# Calculate initial velocity
v = pTo - pFrom
v.normalize()
v *= 1000.0
# Create bullet
shape = BulletBoxShape(Vec3(0.5, 0.5, 0.5))
body = BulletRigidBodyNode('Bullet')
bodyNP = render.attachNewNode(body)
bodyNP.node().addShape(shape)
bodyNP.node().setMass(2.0)
bodyNP.node().setLinearVelocity(v)
bodyNP.setPos(0, pFrom[1], 0)
bodyNP.setCollideMask(BitMask32.allOn())
# Enable CCD
bodyNP.node().setCcdMotionThreshold(1e-7)
# bodyNP.node().setCcdSweptSphereRadius(0.50)
self.world.attachRigidBody(bodyNP.node())
# Remove the bullet again after 1 second
self.bullets.append(bodyNP)
taskMgr.doMethodLater(10, self.removeBullet, 'removeBullet')
def get_horse_task(self, player, actor, dt, task):
# Calculate the vector between
# this actor and the player.
# Also face the player.
vector_to_player = actor.get_pos() - player.get_pos()
vector_to_player2D = vector_to_player.getXz()
vector_to_player2D.normalize()
distanceToPlayer = vector_to_player.length()
heading = self.xVector.signedAngleDeg(vector_to_player2D)
if self.start and not self.states["mount"]:
if distanceToPlayer > self.attackDistance * 0.9:
# Destination is far
self.messenger.send("walk")
vector_to_player.setZ(0)
vector_to_player.normalize()
self.velocity += vector_to_player * self.acceleration * dt
player.set_pos(player.get_pos() + self.velocity * dt)
else:
# Got the destination
self.messenger.send("idle")
self.velocity.set(0, 0, 0)
self.mount_place_pos = player.get_pos()
self.mount_place_hpr = player.get_hpr()
if not self.seq:
# call event for mounting/riding animations sequence
self.messenger.send("mount")
self.start = False
player.set_h(heading)
return task.done
def start_play(self):
self.start = True
def start_journey_task(self, task):
dt = globalClock.getDt()
# Start walking player
if self.start:
self.get_horse_task(self.korlan, self.horse, dt, task)
return task.cont
def set_floater(self):
# Make floater on top of the player to put camera pivot on it
self.floater = NodePath("floater")
self.floater.reparent_to(render)
self.floater.set_pos(0, 0, 1.2)
# camera area node for further distance checks
self.cam_area = NodePath("area")
self.cam_area.reparent_to(self.floater)
self.cam_area.set_z(0)
# camera pivot
self.pivot = NodePath('pivot')
self.pivot.reparent_to(self.floater)
self.pivot.set_z(0)
# hide mouse
self.wp = WindowProperties()
self.wp.set_cursor_hidden(True)
base.win.request_properties(self.wp)
self.start_cam = True
def attach_floater(self, actor):
if actor and self.floater and self.pivot:
# remove horse from floater
if not self.floater.find("**/horserig").is_empty():
# render.find("**/horserig").reparent_to(render)
render.find("**/horserig").reparent_to(self.lod_np)
actor.reparent_to(self.floater)
# restore previous player's Z position
actor.set_pos(0, 0, -1)
actor.get_child(0).set_pos(0, 0, -1)
# camera setup
base.camera.reparent_to(self.pivot)
base.camera.set_pos(0, self.cam_y_back_pos, 0)
def start_tp_cam_task(self, player, task):
if self.start_cam and player:
# TPS Logic
dt = globalClock.getDt()
mouse_direction = base.win.getPointer(0)
x = mouse_direction.get_x()
y = mouse_direction.get_y()
heading = 0
pitch = 0
# Recentering the cursor and do mouse look
if base.win.move_pointer(0, int(base.win.getXSize() / 2), int(base.win.getYSize() / 2)):
# actual calculations
heading = self.pivot.get_h() - (x - int(base.win.getXSize() / 2)) * self.mouse_sens
pitch = self.pivot.get_p() - (y - int(base.win.getYSize() / 2)) * self.mouse_sens
self.pivot.set_r(0)
# apply them to heading and pitch
if not self.is_aiming:
self.pivot.set_h(heading)
if not pitch > 10.0 and not pitch < -50.0:
self.pivot.set_p(pitch)
if self.is_aiming:
# smooth rotate player to cursor
if not int(player.get_h()) - 15:
player.set_h(player, 30)
else:
# world heading in aiming
heading = self.floater.get_h() - (x - int(base.win.getXSize() / 2)) * self.mouse_sens
self.floater.set_h(heading)
else:
player.set_h(0)
"""MOVE CAMERA CLOSE/AWAY TO AVOID OBSTACLES STAYING BEHIND OR FRONT"""
"""# Checking for overlapping
if self.trigger_np:
ghost = self.trigger_np.node()
for node in ghost.getOverlappingNodes():
# ignore player itself and unwanted objects
if node.get_name() != player.get_name() \
and node.get_name() != "Ground" \
and node.get_name() != "horserig" \
and node.get_name() != "cloak":
# this is not very effective way to get NodePath :(
obstacle = render.find("**/{0}".format(node.get_name()))
# smooth move camera close if some obstacle is close to player and vise versa
if player.get_distance(obstacle) <= 2 \
and player.get_distance(obstacle) >= 1:
if int(base.camera.get_y()) > int(
base.camera.get_y()) - 2 and not self.cam_is_close_to_obstacles:
base.camera.set_y(base.camera.get_y() + 7.0 * dt)
else:
if int(base.camera.get_y()) < int(
base.camera.get_y()) + 2 and self.cam_is_close_to_obstacles:
base.camera.set_y(base.camera.get_y() - 7.0 * dt)
# stop transition between these frames if camera is close
if round(player.get_distance(base.camera), 1) < 2.4 \
and round(player.get_distance(base.camera), 1) > 1.2:
self.cam_is_close_to_obstacles = True
# stop transition between these frames if camera is far
if round(player.get_distance(base.camera), 1) > 4.2 \
and round(player.get_distance(base.camera), 1) < 5.8:
self.cam_is_close_to_obstacles = False
else:
# keep default cam y pos until player come close to obstacles again
if base.camera.get_y() != self.cam_y_back_pos \
and not self.cam_is_close_to_obstacles:
base.camera.set_y(base.camera.get_y() - 7.0 * dt)
"""
return task.cont
# Record the state of keys
def setKey(self, key, value):
self.key_map[key] = value
if key == "mouse3-up":
self.states["is_in_sooting"] = True
def player_state_task(self, player, horse, task):
if player and horse:
speed = Vec3(0, 0, 0)
move_unit = 2
if not self.states['mount']:
if self.key_map["left"]:
self.floater.setH(self.floater.getH() + 300 * globalClock.getDt())
elif self.key_map["left"] and self.key_map["forward"]:
self.floater.setH(self.floater.getH() + 300 * globalClock.getDt())
elif self.key_map["right"]:
self.floater.setH(self.floater.getH() - 300 * globalClock.getDt())
elif self.key_map["right"] and self.key_map["forward"]:
self.floater.setH(self.floater.getH() - 300 * globalClock.getDt())
elif self.key_map["forward"]:
speed.setY(-move_unit)
self.floater.setY(self.floater, -move_unit * self.speed * globalClock.getDt())
player.get_parent().set_y(player.get_y())
player.set_play_rate(1.0, "walk")
elif self.key_map["backward"]:
speed.setY(move_unit)
self.floater.setY(self.floater, move_unit * self.speed * globalClock.getDt())
player.get_parent().set_y(player.get_y())
player.set_play_rate(-1.0, "walk")
if not self.key_map["forward"] \
and not self.key_map["backward"] \
and not self.key_map["left"] \
and not self.key_map["right"]:
pass
elif not self.key_map["forward"] \
and not self.key_map["left"] \
or not self.key_map["forward"] \
and not self.key_map["right"]:
pass
elif self.states['mount']:
if self.key_map["left"]:
self.floater.setH(self.floater.getH() + 300 * globalClock.getDt())
# self.messenger.send("left_t")
elif self.key_map["left"] and self.key_map["forward"]:
self.floater.setH(self.floater.getH() + 300 * globalClock.getDt())
# self.messenger.send("left_t")
elif self.key_map["right"]:
self.floater.setH(self.floater.getH() - 300 * globalClock.getDt())
# self.messenger.send("right_t")
elif self.key_map["right"] and self.key_map["forward"]:
self.floater.setH(self.floater.getH() - 300 * globalClock.getDt())
# self.messenger.send("right_t")
elif self.key_map["forward"]:
speed.setY(-move_unit)
self.floater.setY(self.floater, -move_unit * self.speed * globalClock.getDt())
horse.get_parent().set_y(horse.get_y())
horse.set_play_rate(1.0, "walk")
# self.messenger.send("walk")
elif self.key_map["backward"]:
speed.setY(move_unit)
self.floater.setY(self.floater, move_unit * self.speed * globalClock.getDt())
horse.get_parent().set_y(horse.get_y())
horse.set_play_rate(-1.0, "walk")
# self.messenger.send("walk")
if not self.key_map["forward"] \
and not self.key_map["backward"] \
and not self.key_map["left"] \
and not self.key_map["right"]:
player.stop("walk")
player.stop("left_t")
player.stop("right_t")
self.messenger.send("idle")
elif not self.key_map["forward"] \
and not self.key_map["left"] \
or not self.key_map["forward"] \
and not self.key_map["right"]:
self.messenger.send("idle")
if self.has_weapon['sword'] and self.key_map["melee_attack"] \
and not self.key_map["archery_attack"]:
self.messenger.send("slash")
elif self.has_weapon["bow"] and self.key_map["archery_attack"] \
and not self.key_map["melee_attack"]:
self.messenger.send("draw_bow")
current_anim = player.get_current_anim()
if player.getAnimControl(current_anim) and not player.getAnimControl(current_anim).is_playing():
player.stop(current_anim)
self.messenger.send("idle")
return task.cont
def anim_state(self, actor, state, loop):
if isinstance(state, str) and isinstance(loop, int):
self.stateControl = actor.getAnimControl(state)
if self.stateControl and not self.stateControl.isPlaying():
# Animation blending, take anim names from base.anim_collector()
# Play modes
if loop == 1:
actor.loop(state)
elif loop == 0:
actor.play(state)
def draw_bow_anim_state(self, actor, state, loop):
if isinstance(state, str) and isinstance(loop, int):
self.stateControl = actor.getAnimControl(state)
if self.stateControl and not self.stateControl.isPlaying():
# Animation blending, take anim names from base.anim_collector()
if self.draw_bow_is_done == 0:
actor.play(state)
self.draw_bow_is_done = 1
def floater_attach_to(self, event):
if event and isinstance(event, str):
self.messenger.send(event)
self.states["mount"] = True
def floater_detach_from(self, event):
if event and isinstance(event, str):
self.messenger.send(event)
self.states["mount"] = False
def interact(self, parent, child, bone):
if parent and child and bone:
if not self.states["mount"]:
self.mount_seq(parent, child, bone)
elif self.states["mount"]:
self.unmount_seq(parent, child, bone)
def mount_seq(self, parent, child, bone):
if parent and child and bone and self.states["is_ready_to_be_used"]:
duration = child.get_duration(animName="mount")
delay = Wait(duration)
heading = child.get_h()
# with inverted Z -0.5 stands for Up
# Our horse (un)mounting animations have been made with imperfect positions,
# so, I had to change child positions to get more satisfactory result
# with these animations in my game.
mounting_pos = Vec3(0.6, -0.15, -0.45)
saddle_pos = Vec3(0, -0.28, 0.08)
parent_pos = Vec3(parent.get_x(), parent.get_y(), -1)
self.seq = Sequence(Func(child.reparent_to, parent),
Func(parent.set_pos, parent_pos),
Func(child.set_pos, mounting_pos),
Func(child.set_h, heading),
Func(self.floater_attach_to, "attach_floater_to_horse"),
Func(self.floater.set_z, 2),
Parallel(Func(parent.set_pos, parent_pos),
Func(self.anim_state, child, "mount", 0), delay),
Func(parent.set_pos, parent_pos),
Func(child.set_pos, saddle_pos),
Func(self.anim_state, child, "horse_riding", 1),
Func(self.finish_sequence, self.seq)
)
self.seq.start()
self.is_mount_seq_done = True
self.is_unmount_seq_done = False
self.states["is_ready_to_be_used"] = False
def unmount_seq(self, parent, child, bone):
if parent and child and bone:
duration = child.get_duration(animName="unmount")
delay = Wait(duration)
heading = -90
# with inverted Z -0.7 stands for Up
# Our horse (un)mounting animations have been made with imperfect positions,
# so, I had to change child positions to get more satisfactory result
# with these animations in my game.
unmounting_pos = Vec3(0.6, -0.15, -0.45)
saddle_pos = Vec3(0, -0.28, 0.08)
child_pos_for_flt = Vec3(0.5, 0.1, 1.2)
parent_pos = Vec3(parent.get_x(), parent.get_y(), 0)
# Reparent parent/child node back to its BulletCharacterControllerNode
parent_bs_name = "**/{0}:BS".format(parent.get_name())
child_bs_name = "**/{0}:BS".format(child.get_name())
if not render.find(parent_bs_name).is_empty() \
and not render.find(child_bs_name).is_empty():
parent_bs = render.find(parent_bs_name)
child_bs = render.find(child_bs_name)
self.seq = Sequence(Func(child.set_pos, saddle_pos),
Func(child.set_h, heading),
Func(child.set_pos, unmounting_pos),
Parallel(Func(self.anim_state, child, "unmount", 0), delay),
Func(self.floater_detach_from, "attach_floater_to_player"),
Func(child.reparent_to, child_bs),
Func(child.set_z, -1),
Func(self.floater.set_pos, child_pos_for_flt),
Func(parent.reparent_to, parent_bs),
Func(parent.set_pos, parent_pos),
Func(child.set_x, 0),
Func(self.anim_state, child, "idle", 1),
Func(self.finish_sequence, self.seq)
)
self.seq.start()
self.is_mount_seq_done = False
self.is_unmount_seq_done = True
def finish_sequence(self, seq):
if seq:
seq.finish()
def update_cam_trigger_once_task(self, task):
if self.cam_area and self.trigger_np:
self.trigger_np.reparent_to(self.cam_area)
self.trigger_np.set_z(0)
return task.done
return task.cont
def update_npc_trigger_task(self, actor, target_actor, task):
if actor and target_actor:
if not actor.find("**/horserig_trigger").is_empty():
trigger = actor.find("**/horserig_trigger").node()
trigger_np = actor.find("**/horserig_trigger")
for node in trigger.getOverlappingNodes():
# ignore trigger itself and ground both
if node.get_name() != trigger.get_name() \
and node.get_name() != "Ground":
if node.get_name() == target_actor:
self.states["is_in_mounting"] = True
else:
self.states["is_in_mounting"] = False
# if player close to horse
if self.korlan and self.korlan_bs:
if self.korlan.get_distance(trigger_np) <= 2 \
and self.korlan.get_distance(trigger_np) >= 1:
if not self.states["mount"] and node.get_name() == self.korlan.get_name():
self.states["is_ready_to_be_used"] = True
elif not self.states["mount"] and node.get_name() == self.korlan_bs.get_name():
self.states["is_ready_to_be_used"] = True
return task.cont
def set_player_controller(self, actor, joints):
if actor and self.world:
actor_bs = self.get_bs_capsule(h=2, w=0.3)
col_name = "{0}:BS".format(actor.get_name())
if not self.korlan_bs and not hasattr(base, "bullet_char_contr_node"):
base.bullet_char_contr_node = BulletCharacterControllerNode(actor_bs,
0.4,
col_name)
self.korlan_bs = self.render.attach_new_node(base.bullet_char_contr_node)
self.korlan_bs.set_collide_mask(BitMask32.bit(0))
self.world.attach(base.bullet_char_contr_node)
actor.reparent_to(self.korlan_bs)
actor.set_z(0)
# self.korlan_bs.reparent_to(self.lod_np)
"""if joints:
self.set_bs_hitbox(actor=actor,
joints=joints,
world=self.world)"""
elif self.korlan_bs and hasattr(base, "bullet_char_contr_node") and base.bullet_char_contr_node:
actor.reparent_to(self.korlan_bs)
# self.korlan_bs.reparent_to(self.lod_np)
actor.set_z(0)
def set_npc_controller(self, actor, joints):
if actor and self.world:
bullet_shape = self.get_bs_capsule(h=1.75, w=0.3)
col_name = "{0}:BS".format(actor.get_name())
base.bullet_controller_node = BulletCharacterControllerNode(bullet_shape,
0.4,
col_name)
actor_bs_np = self.render.attach_new_node(base.bullet_controller_node)
actor_bs_np.set_collide_mask(BitMask32.bit(0))
self.world.attach(base.bullet_controller_node)
actor.reparent_to(actor_bs_np)
# actor.reparent_to(self.lod_np)
# Set actor down to make it
# at the same point as bullet shape
actor.set_z(-1)
# Set the bullet shape position same as actor position
if actor_bs_np:
actor_bs_np.set_x(0)
actor_bs_np.set_y(3)
# Set actor position to zero
# after actor becomes a child of bullet shape.
# It should not get own position values.
actor.set_y(0)
actor.set_x(0)
if joints:
self.set_bs_hitbox(actor=actor,
joints=joints,
world=self.world)
def set_ghost_trigger(self, actor):
if actor:
radius = 1.75 - 2 * 0.3
sphere = BulletSphereShape(radius)
trigger_bg = BulletGhostNode('{0}_trigger'.format(actor.get_name()))
trigger_bg.addShape(sphere)
trigger_np = self.render.attachNewNode(trigger_bg)
trigger_np.setCollideMask(BitMask32(0x0f))
self.world.attachGhost(trigger_bg)
trigger_np.reparent_to(actor)
trigger_np.setPos(0, 0, 0)
trigger_np.set_pos(0, 0, 1)
def get_bs_capsule(self, h, w):
if h and w:
height = h
width = w
radius = height - 2 * width
capsule = BulletCapsuleShape(width, radius, ZUp)
return capsule
def get_bs_sphere(self, r):
if r:
radius = r
sphere = BulletSphereShape(radius)
return sphere
def remove_all_weapons_to_spine(self, actor, bone):
if actor and bone and isinstance(bone, str):
sword = render.find("**/sword")
bow = render.find("**/bow")
arrow = render.find("**/bow_arrow")
# usually we should record item positions and attached hands somewhere to dict,
# because hands may change
sp_joint = actor.exposeJoint(None, "modelRoot", bone)
if sp_joint and sword and bow and arrow:
sword.reparent_to(sp_joint)
bow.reparent_to(sp_joint)
arrow.reparent_to(sp_joint)
# positioning and scaling
sword.set_pos(10, 20, -8)
sword.set_hpr(325.30, 343.30, 7.13)
sword.set_scale(100)
bow.set_pos(0, 12, -12)
bow.set_hpr(78.69, 99.46, 108.43)
bow.set_scale(100)
arrow.set_pos(-10, 7, -12)
arrow.set_hpr(91.55, 0, 0)
arrow.set_scale(100)
for v in self.has_weapon:
self.has_weapon[v] = False
def get_weapon(self, actor, name, bone):
if name and isinstance(name, str) and actor and bone:
h_joint = actor.exposeJoint(None, "modelRoot", bone)
sword = render.find("**/sword")
bow = render.find("**/bow")
arrow = render.find("**/bow_arrow")
if sword and bow and arrow:
self.remove_all_weapons_to_spine(actor=actor, bone="Korlan:Spine1")
if name == "sword":
# use sword then
sword.reparent_to(h_joint)
# rescale weapon because it's scale 100 times smaller than we need
sword.set_scale(100)
sword.set_pos(-11.0, 13.0, -3.0)
sword.set_hpr(212.47, 0.0, 18.43)
self.has_weapon["sword"] = True
self.has_weapon["bow"] = False
elif name == "bow":
# use bow then
bow.reparent_to(h_joint)
# rescale weapon because it's scale 100 times smaller than we need
bow.set_scale(100)
bow.set_pos(0, 2.0, 2.0)
bow.set_hpr(216.57, 293.80, 316.85)
if not render.find("**/bow_arrow").is_empty():
arrow = render.find("**/bow_arrow")
arrow.reparent_to(bow)
# rescale weapon because it's scale 100 times smaller than we need
bow.set_scale(100)
arrow.set_scale(1)
arrow.set_pos(0.04, 0.01, -0.01)
arrow.set_hpr(0, 2.86, 0)
arrow.set_python_tag("power", 0)
arrow.set_python_tag("ready", 0)
arrow.set_python_tag("shot", 0)
arrow.set_python_tag("owner", "player")
self.has_weapon["sword"] = False
self.has_weapon["bow"] = True
def drop_item(self, actor):
# usually we should record item positions and attached hands somewhere to dict,
# because hands may change
if actor:
lh_joint = actor.exposeJoint(None, "modelRoot", "Korlan:LeftHand")
rh_joint = actor.exposeJoint(None, "modelRoot", "Korlan:RightHand")
sword = render.find("**/sword")
bow = render.find("**/bow")
arrow = render.find("**/bow_arrow")
if sword and bow and arrow:
sword.reparent_to(self.lod_np)
sword.set_pos(lh_joint.get_x(), lh_joint.get_y(), 0)
bow.reparent_to(self.lod_np)
bow.set_pos(rh_joint.get_x(), rh_joint.get_y(), 0)
arrow.reparent_to(self.lod_np)
arrow.set_pos(rh_joint.get_x(), rh_joint.get_y(), 0)
# revert to original scale
sword.set_scale(1)
bow.set_scale(1)
arrow.set_scale(1)
def attack(self, actor, anim):
if actor and anim:
delay = Wait(3)
# self.anim_state(actor, "slash", 0)
# actor.set_control_effect("slash", 1)
# we have weapon
if actor.getNumChildren():
self.seq = Sequence(Parallel(Func(self.anim_state, actor, anim, 0), delay),
Func(self.anim_state, actor, "idle", 1),
Func(self.finish_sequence, self.seq))
self.seq.start()
def set_bs_hitbox(self, actor, joints, world):
if actor and joints and world:
for joint in joints:
shape = BulletBoxShape(Vec3(1, 1, 1))
name_hb = "{0}_{1}:HB".format(actor.get_name(), joint)
name = actor.get_name()
ghost = BulletGhostNode(name_hb)
ghost.add_shape(shape)
ghost_np = render.attachNewNode(ghost)
if joint == "Hips":
ghost_np.set_pos(0, 0, 0)
ghost_np.set_scale(15, 15, 15)
elif joint == "LeftHand" or joint == "RightHand":
ghost_np.set_pos(0, 8.0, 5.2)
ghost_np.set_scale(6, 6, 6)
mask = actor.get_collide_mask()
ghost_np.node().set_into_collide_mask(mask)
ghost_np.set_tag(key=name_hb, value=joint)
exposed_joint = None
if name == "Player":
char_joint = self.korlan.get_part_bundle('modelRoot').get_name()
joint = "{0}:{1}".format(char_joint, joint)
exposed_joint = self.korlan.expose_joint(None, "modelRoot", joint)
elif name != "Player":
char_joint = actor.get_part_bundle('modelRoot').get_name()
joint = "{0}:{1}".format(char_joint, joint)
exposed_joint = actor.expose_joint(None, "modelRoot", joint)
ghost_np.reparent_to(exposed_joint)
world.attach_ghost(ghost)
# self.base.actor_hb[name_hb] = ghost_np.node()
# self.base.actor_hb_masks[name_hb] = mask
def update_physics_task(self, task):
dt = globalClock.getDt()
self.world.doPhysics(dt)
# Prevent character offsetting from camera center
# self.korlan_bs.set_x(0)
return task.cont
def toggle_physics_debug(self):
if self.debug_nodepath:
if self.debug_nodepath.is_hidden():
self.debug_nodepath.show()
else:
self.debug_nodepath.hide()
def world_setup(self):
self.world = BulletWorld()
self.world.setGravity(Vec3(0, 0, -9.81))
self.world.set_group_collision_flag(0, 0, False)
self.world.set_group_collision_flag(0, 1, True)
self.world.set_group_collision_flag(1, 1, True)
# Plane
shape = BulletPlaneShape(Vec3(0, 0, 1), 0)
node = BulletRigidBodyNode('Ground')
node.addShape(shape)
np = render.attachNewNode(node)
np.setPos(0, 0, 0)
self.world.attachRigidBody(node)
# Box
shape = BulletBoxShape(Vec3(1, 1, 1))
node = BulletRigidBodyNode('Box')
node.setMass(50.0)
node.addShape(shape)
np = render.attachNewNode(node)
np.setPos(4, 2, 0)
self.world.attachRigidBody(node)
model = base.loader.loadModel('/home/galym/Korlan/tmp/box.egg')
model.reparentTo(np)
model.setName('box')
model.setPos(0, 0, 0)
model.setHpr(np.getHpr())
np.setCollideMask(BitMask32.bit(1))
# Characters
self.set_player_controller(actor=self.korlan, joints=["LeftHand", "RightHand", "Hips"])
# Horse
self.set_npc_controller(actor=self.horse, joints=[])
self.set_ghost_trigger(self.horse)
# Show a visual representation of the collisions occuring
self.debugNP = render.attachNewNode(BulletDebugNode('Debug'))
self.debugNP.show()
# self.debugNP.node().showWireframe(True)
# self.debugNP.node().showConstraints(True)
# self.debugNP.node().showBoundingBoxes(False)
# self.debugNP.node().showNormals(True)
self.world.setDebugNode(self.debugNP.node())
def cursor_state_task(self, task):
if self.has_weapon['bow'] \
and not self.key_map["melee_attack"] \
and self.key_map["archery_attack"]:
self.cursor_ui.show()
base.camera.set_x(0.5)
base.camera.set_y(-2)
self.is_aiming = True
if self.has_weapon['bow'] \
and not self.key_map["melee_attack"] \
and not self.key_map["archery_attack"] \
and self.is_aiming:
# self.cursor_ui.hide()
base.camera.set_x(0)
base.camera.set_y(self.cam_y_back_pos)
self.is_aiming = False
return task.cont
def prepare_arrow(self):
if self.bow:
self.arrow = base.loader.loadModel('{0}/Assets/Weapons/bow_arrow.egg'.format(self.game_dir))
self.arrow.set_name("bow_arrow")
self.arrow.reparent_to(self.bow)
self.arrow.set_pos(0.04, -0.01, -0.01)
self.arrow.set_hpr(0, 2.86, 0)
self.arrow.set_python_tag("power", 0)
self.arrow.set_python_tag("ready", 0)
self.arrow.set_python_tag("shot", 0)
self.arrow.set_python_tag("owner", "player")
def destroy_arrow(self):
self.arrow.remove_node()
# if arrow_name and render.find("**{0}".format(arrow_name)):
# render.find("**{0}".format(arrow_name)).remove_node()
def arrow_hit_check(self, task):
if self.arrow:
mouse_watch = base.mouseWatcherNode
if mouse_watch.has_mouse():
pos_mouse = base.mouseWatcherNode.get_mouse()
pos_from = Point3()
pos_to = Point3()
base.camLens.extrude(pos_mouse, pos_from, pos_to)
pos_from = self.render.get_relative_point(self.korlan_bs, pos_from)
pos_to = self.render.get_relative_point(base.camera, pos_to)
self.raytest_result = self.world.rayTestClosest(pos_from, pos_to)
if self.raytest_result.get_node() \
and "Ground" not in str(self.raytest_result.get_node().get_name()) \
and "Korlan" not in str(self.raytest_result.get_node().get_name()):
self.hit_target = self.raytest_result.get_node()
self.target_pos = self.raytest_result.get_hit_pos()
if self.hit_target:
self.target_test_ui.setText(self.hit_target.get_name())
else:
self.target_test_ui.setText("")
if hasattr(base, "body_np"):
self.world.contactTest(self.hit_target)
if self.world.contactTest(self.hit_target).getNumContacts() > 0:
base.body_np.node().setLinearVelocity(0)
if self.arrow.get_python_tag("ready") == 1:
if hasattr(base, "target_np"):
self.arrow.wrt_reparent_to(base.target_np)
return task.cont
def bow_shoot(self):
if self.arrow and self.target_pos and self.hit_target:
name = self.hit_target.get_name()
base.target_np = render.find("**/{0}".format(name))
self.arrow.set_python_tag("shot", 1)
# Create bullet
shape = BulletBoxShape(Vec3(0.05, 0.5, 0.05))
body = BulletRigidBodyNode('Arrow')
base.body_np = NodePath(body)
base.body_np.wrt_reparent_to(self.bow)
base.body_np.setPos(self.arrow.get_pos())
base.body_np.setHpr(self.arrow.get_hpr())
base.body_np.setScale(self.arrow.get_scale())
self.arrow.wrt_reparent_to(base.body_np)
base.body_np.node().addShape(shape)
base.body_np.node().setMass(2.0)
# base.body_np.setCollideMask(BitMask32.allOn())
base.body_np.setCollideMask(BitMask32.bit(0))
# Enable CCD
base.body_np.node().setCcdMotionThreshold(1e-7)
base.body_np.node().setCcdSweptSphereRadius(0.50)
self.world.attachRigidBody(base.body_np.node())
base.body_np.wrt_reparent_to(render)
Sequence(Wait(1), Func(self.prepare_arrow)).start()
def arrow_fly_task(self, task):
dt = globalClock.getDt()
if self.arrow:
power = self.arrow.get_python_tag("power")
if power and self.arrow.get_python_tag("shot") == 1:
base.body_np.set_x(base.body_np, -power * dt)
return task.cont
def charge_arrow_task(self, task):
if self.key_map["melee_attack"] \
and self.key_map["archery_attack"]:
if self.arrow:
power = self.arrow.get_python_tag("power")
power += 1
self.arrow.set_python_tag("power", power)
self.arrow.set_python_tag("ready", 1)
if not self.key_map["melee_attack"] and self.key_map["archery_attack"]:
if self.arrow and self.arrow.get_python_tag("ready") == 1:
if self.arrow.get_python_tag("shot") == 0:
self.bow_shoot()
if not self.key_map["melee_attack"] and not self.key_map["archery_attack"]:
if self.draw_bow_is_done == 1:
self.draw_bow_is_done = 0
self.korlan.stop("draw_bow")
return task.cont
def reset_arrow_charge(self):
if self.arrow:
self.arrow.set_python_tag("power", 0)
app = Application()
app.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment