Created
January 4, 2022 00:42
-
-
Save kergalym/f9bef48e950d2e046620d13e1ef636f8 to your computer and use it in GitHub Desktop.
bow_shoot_test for panda3d
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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