codetower/ct.py

706 lines
32 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import importlib
import bge # Blender Game Engine (UPBGE)
import bpy # Blender
import aud # Sounds
import math
import time
import sys
import os
import webbrowser
import threading # Multithreading
import xml.etree.ElementTree as ET # Creating/parsing XML file
###############################################################################
# ct.py
# @title: the CodeTower game
# @project: CodeTower
# @lang: fr,en
# @authors: Philippe Roy <philippe.roy@ac-grenoble.fr>
# @copyright: Copyright (C) 2022 Philippe Roy
# @license: GNU GPL
#
# This game is a tower defense coding game. The towers are driven by Python code.
# Ce simulateur est un jeu du type tower defense où les tours sont à piloter par la programmation Python.
#
# Commands trigged by button : cmd_*
# Commands trigged by 3D scene objects : scn_*
# Commands trigged by user (student or map designer) : ct_*
# 3D scene manipulation : manip_*
#
###############################################################################
# Dynamic import user file
sys.setrecursionlimit(10**5) # Limite sur la récursivité (valeur par défaut : 1000) -> segfault de Blender
importlib.invalidate_caches()
ct_map = importlib.import_module('ct_map1') # waves script
ct_cmd = importlib.import_module('ct_cmd') # user script (commands)
# UPBGE scene
scene = bge.logic.getCurrentScene()
eevee = bpy.context.scene.eevee
# Config file
# print (os.getcwd())
ct_config = ET.parse('ct_config.xml')
ct_config_tree = ct_config.getroot()
# Colors
color_magenta = [0.800, 0.005, 0.315,1]
color_orange = [0.799, 0.130, 0.063,1]
color_white = [0.8, 0.8, 0.8, 1]
color_yellow = [0.8, 0.619, 0.021, 1]
color_black = [0, 0, 0, 1]
color_kaykit_black = [0.019, 0.032, 0.037, 1]
color_endbanner_bluelight = [0.361, 0.527, 0.716, 1]
color_endbanner_bluedark = [0.130, 0.254, 0.407, 1]
color_text = [0, 0, 0, 1] # Noir
color_text_red = [0.799, 0.031, 0.038, 1]
color_cmd = [0.8, 0.8, 0.8, 1] # blanc
color_cmd_hl = [0.8, 0.619, 0.021, 1] # jaune
color_link = [0.024, 0.006, 0.8, 1] # bleu
color_link_hl = [0.8, 0.005, 0.315, 1] # majenta
# Sounds
audiodev = aud.Device()
snd_click = aud.Sound('asset/sounds/click.ogg')
sndbuff_click = aud.Sound.cache(snd_click)
snd_construct = aud.Sound('asset/sounds/click_construct.ogg')
sndbuff_construct = aud.Sound.cache(snd_construct)
# UPBGE constants
JUST_ACTIVATED = bge.logic.KX_INPUT_JUST_ACTIVATED
JUST_RELEASED = bge.logic.KX_INPUT_JUST_RELEASED
ACTIVATE = bge.logic.KX_INPUT_ACTIVE
# JUST_DEACTIVATED = bge.logic.KX_SENSOR_JUST_DEACTIVATED
###############################################################################
# Tour
###############################################################################
# Commande pour afficher la position de la tour de construction
def cmd_tower_construct(cont):
obj = cont.owner
obj_Hl= scene.objects[obj.name+"-Hl"]
if cont.sensors['Click'].status == JUST_ACTIVATED and cont.sensors['MO'].positive and scene.objects['Terrain']['manip_mode']==0:
audiodev.play(sndbuff_construct)
if scene.objects['Terrain']['construct_mode']==True:
scene.objects['Terrain']['construct_mode']=False
obj.worldScale=[1, 1, 1]
obj.color = color_cmd
obj_Hl.worldScale=[1, 1, 1]
obj_Hl.color = color_cmd
scene.objects['Tower_construc_mode'].setVisible(False,False)
scene.objects['Tower_construc_mode'].color = color_cmd
text_info ("")
else:
scene.objects['Terrain']['construct_mode']=True
obj.worldScale=[1.25, 1.25, 1.25]
obj.color = color_cmd_hl
obj_Hl.worldScale=[1.25, 1.25, 1.25]
obj_Hl.color = color_cmd_hl
scene.objects['Tower_construc_mode'].setVisible(True,False)
scene.objects['Tower_construc_mode'].color = color_cmd
text_info ("Tower position : ")
###############################################################################
# User interface : texte info et compteurs
###############################################################################
# Affichage sur la boite de texte sur 6 lignes
def text_info (text):
if text=="":
scene.objects['Text_info-1'].setVisible(False,False)
scene.objects['Text_info-2'].setVisible(False,False)
else:
lines_txt=text.split("\n", 6)
for i in range (len(lines_txt),6):
lines_txt.append("")
scene.objects['Text_info-1'].setVisible(True,False)
scene.objects['Text_info-2'].setVisible(True,False)
scene.objects['Text_info-1']['Text']=lines_txt[0]+"\n"+lines_txt[1]+"\n"+lines_txt[2]
scene.objects['Text_info-2']['Text']=lines_txt[3]+"\n"+lines_txt[4]+"\n"+lines_txt[5]
# Mise à jour de l'affichage des compteurs
def points_maj (cont):
scene.objects['Lifes_text']['Text']=str(scene.objects['Points']['lifes'])+"/"+str(scene.objects['Points']['lifes_max'])
scene.objects['Coins_text']['Text']=str(scene.objects['Points']['coins'])
scene.objects['Level_text']['Text']=str(scene.objects['Points']['level'])+"/"+str(scene.objects['Points']['level_max'])
scene.objects['Minions_text']['Text']=str(scene.objects['Points']['minions'])
scene.objects['Points']['tics'] +=1
# Augmentation d'un niveau
if scene.objects['Points']['coins']>=100:
scene.objects['Points']['level_max'] +=1
scene.objects['Points']['coins'] -=100
# Level trop élevé
if scene.objects['Points']['level'] > scene.objects['Points']['level_max'] :
scene.objects['Level_text'].color = color_text_red
if scene.objects['Points']['level'] <= scene.objects['Points']['level_max']:
scene.objects['Level_text'].color = color_text
# Ramasse minions perdues
if scene.objects['Points']['tics']%120 ==0: # Toutes les 2 secondes
scene.objects['Points']['minions_lost']=[]
for obj_i in scene.objects:
if "type_minion" in obj_i.getPropertyNames() and "type_towerminion" not in obj_i.getPropertyNames():
if obj_i.worldLinearVelocity.x >0 and abs(obj_i.worldLinearVelocity.x) < 0.001 and obj_i.worldLinearVelocity.y >0 and abs(obj_i.worldLinearVelocity.y) < 0.001:
print ("Minion lost : x' or y' very slow (x, y, z, x', y') : ", obj_i.name, obj_i.worldPosition.x, obj_i.worldPosition.y, obj_i.worldPosition.z, abs(obj_i.worldLinearVelocity.x), abs(obj_i.worldLinearVelocity.y))
obj_i['dead']=True
elif obj_i.worldPosition.x<scene.objects['Terrain']['size'][0] or obj_i.worldPosition.x>scene.objects['Terrain']['size'][1] or obj_i.worldPosition.y<scene.objects['Terrain']['size'][2] or obj_i.worldPosition.y>scene.objects['Terrain']['size'][3]:
print ("Minion lost : x or y outside the map (x, y, z, x', y') : ", obj_i.name, obj_i.worldPosition.x, obj_i.worldPosition.y, obj_i.worldPosition.z, abs(obj_i.worldLinearVelocity.x), abs(obj_i.worldLinearVelocity.y))
obj_i['dead']=True
# Fin de la vague
if scene.objects['Terrain']['thread_wave']==False and scene.objects['Terrain']['map_run'] == True :
if scene.objects['Points']['minions_run']==0 :
# Fin ou vague suivante
if scene.objects['Points']['lifes'] == 0 or scene.objects['Points']['wave'] == scene.objects['Terrain']['nb_waves'] :
terrain_end ()
else:
scene.objects['Points']['wave'] +=1
ct_map.start(scene.objects['Points']['wave']) # Lancement du script de la vague
###############################################################################
# Terrain
###############################################################################
# Mouse over du terrain
def scn_terrain_mo (cont):
# Affiche la position de la tour de construction
if scene.objects['Terrain']['construct_mode']==True:
hitObject = cont.sensors['MO'].hitObject
hitPosition = cont.sensors['MO'].hitPosition
if hitObject is not None :
if round(hitPosition.x) >= scene.objects['Terrain']['size'][0] and round(hitPosition.x) <= scene.objects['Terrain']['size'][1] and round(hitPosition.y) >= scene.objects['Terrain']['size'][2] and round(hitPosition.y) <= scene.objects['Terrain']['size'][3] :
if [round(hitPosition.x),round(hitPosition.y)] in scene.objects['Terrain']['scene_tile_noncontruct'] or [round(hitPosition.x),round(hitPosition.y)] in scene.objects['Terrain']['scene_tile_tower']:
pass
else:
scene.objects['Tower_construc_mode'].worldPosition.x=round(hitPosition.x)
scene.objects['Tower_construc_mode'].worldPosition.y=round(hitPosition.y)
scene.objects['Tower_construc_mode'].worldPosition.z=0.2
text_info ("Tower position : "+str(round(hitPosition.x))+","+str(round(hitPosition.y)))
# Affiche les informations sur la tour
# FIXME : High-light sur la tower sélectionnée
else:
hitPosition = cont.sensors['MO'].hitPosition
if cont.sensors['Click'].status == JUST_ACTIVATED and cont.sensors['MO'].positive and scene.objects['Terrain']['manip_mode']==0:
if [round(hitPosition.x),round(hitPosition.y)] in scene.objects['Terrain']['scene_tile_tower']:
for obj_i in scene.objects:
if "type_tower" in obj_i.getPropertyNames():
if round(hitPosition.x) == obj_i.worldPosition.x and round(hitPosition.y) == obj_i.worldPosition.y:
text =obj_i['tower_name'] + "\n" + "- level : "+ str(obj_i['lvl']) + "\n" + "- damage : "+ str(obj_i['damage']) + "\n" + "- speed : "+ str(round(obj_i['speed'],2)) + "\n" + "- range : "+ str(obj_i['range'])
text_info (text)
break
else:
text_info ("")
# Initialisation lors du chargement du terrain
def terrain_init (cont):
# Cacher la bannière de fin
scene.objects['End'].setVisible(False,True)
scene.objects['End']['timer']=0
# Pile des draws
scene.objects['Terrain']['draw_process']=False
scene.objects['Terrain']['draw_list']=[]
# Ramasse-miettes
scene.objects['Points']['minions_lost']=[]
# Configuration du moteur de rendu
if scene.objects['Terrain']['speed']<4: # smaa avec en vitesse 4 et 10 -> tendance au plantage
eevee.use_eevee_smaa = True
else:
eevee.use_eevee_smaa = False
# Recherche les tuiles non constructibles (chemin)
scene.objects['Terrain']['scene_tile_noncontruct'] = []
for obj_i in scene.objects:
if "tile_straight" in obj_i.name:
scene.objects['Terrain']['scene_tile_noncontruct'].append([obj_i.worldPosition.x, obj_i.worldPosition.y])
obj_i.collisionGroup=2
if "tile_cornerRound" in obj_i.name:
scene.objects['Terrain']['scene_tile_noncontruct'].append([obj_i.worldPosition.x, obj_i.worldPosition.y])
obj_i.collisionGroup=2
if "tile_hill" in obj_i.name:
obj_i.collisionGroup=2
scene.objects['Terrain']['scene_tile_tower']= []
# Init de la carte
ct_map.map_init()
ct_map.map_reset()
scene.objects['Level_text'].color = color_text
# Mise en route et pause du cycle
def terrain_run ():
audiodev.play(sndbuff_click)
# Pause
if scene.objects['Terrain']['run'] == True:
scene.objects['Terrain']['run']=False
scene.objects['Pause'].setVisible(False,False)
scene.objects['Pause-Hl'].setVisible(False,False)
scene.objects['Run-Hl'].setVisible(True,False)
for obj_i in scene.objects: # Pause des Steerings
if "type_minion" in obj_i.getPropertyNames() and "type_towerminion" not in obj_i.getPropertyNames():
obj_i.actuators['Steering'].velocity=0
# Run
else :
scene.objects['Terrain']['run']=True
scene.objects['Run'].setVisible(False,False)
scene.objects['Run-Hl'].setVisible(False,False)
scene.objects['Pause-Hl'].setVisible(True,False)
# Démarrage de la map
if scene.objects['Terrain']['thread_run']==False:
scene.objects['Stop'].setVisible(True,False)
# Mise à zéro des compteurs
ct_map.map_reset()
# Supprimer les tours
for obj_i in scene.objects:
if "type_tower" in obj_i.getPropertyNames() :
obj_i.endObject()
if "type_towerminion" in obj_i.getPropertyNames() :
obj_i.endObject()
scene.objects['Terrain']['scene_tile_tower']= []
# Scripts utilisateur et vagues
scene.objects['Terrain']['map_run'] = True
scene.objects['Terrain']['thread_run']=True
scene.objects['Points']['time_begin']=time.localtime()
if scene.objects['Terrain']['debug_flag']==False: # Lecture dynamique du script python (risque de Segfault de Blender)
importlib.reload(ct_cmd)
# importlib.reload(ct_map)
ct_cmd.start() # Execution du script utilisateur
ct_map.start(1) # Lancement du script de la permière vague
# Arrêt de la pause
else:
for obj_i in scene.objects: # Relance des Steerings
if "type_minion" in obj_i.getPropertyNames() and "type_towerminion" not in obj_i.getPropertyNames():
obj_i.actuators['Steering'].velocity=obj_i['speed_base']*scene.objects['Terrain']['speed']
# Arrêt et réinitialisation du cycle
def terrain_stop ():
audiodev.play(sndbuff_click)
# Arrêt des threads utilisateurs
scene.objects['Terrain']['run']=False
scene.objects['Terrain']['thread_run']=False
scene.objects['Terrain']['map_run'] = False # Ne pas afficher la bannière de fin
ct_cmd.stop() # Stop du script utilisateur
ct_map.stop() # Stop du script des vagues
# Supprimer les enemis
for obj_i in scene.objects:
if "type_minion" in obj_i.getPropertyNames() and "type_towerminion" not in obj_i.getPropertyNames():
obj_i.endObject()
scene.objects['Points']['minions']=0
scene.objects['Points']['minions_run']=0
# Commandes
scene.objects['Pause'].setVisible(False,False)
scene.objects['Pause-Hl'].setVisible(False,False)
scene.objects['Run'].setVisible(True,False)
scene.objects['Stop'].setVisible(False,False)
scene.objects['Stop-Hl'].setVisible(False,False)
# Fin de la map
def terrain_end ():
scene.objects['Terrain']['run']=False
scene.objects['Terrain']['thread_run']=False
# Commandes
scene.objects['Pause'].setVisible(False,False)
scene.objects['Pause-Hl'].setVisible(False,False)
scene.objects['Run'].setVisible(True,False)
scene.objects['Stop'].setVisible(False,False)
scene.objects['Stop-Hl'].setVisible(False,False)
# Affichage des résultats
if scene.objects['End']['timer']== 0:
scene.objects['Terrain']['manip_mode']=9 # Fenêtre modale
scene.objects['Camera'].worldPosition.x = scene.objects['Camera']['init_lx']
scene.objects['Camera'].worldPosition.y = scene.objects['Camera']['init_ly']
scene.objects['Camera'].worldPosition.z = scene.objects['Camera']['init_lz']
maptime_mn=time.localtime().tm_min-scene.objects['Points']['time_begin'].tm_min
maptime_sec=time.localtime().tm_sec-scene.objects['Points']['time_begin'].tm_sec
if maptime_sec<0:
maptime_mn-=1
maptime_sec+=60
scene.objects['Endbanner_wave_text']['Text']="Wave \n"+str(scene.objects['Points']['wave'])
if maptime_mn<=99:
scene.objects['Endbanner_points_text']['Text']="Level "+str(scene.objects['Points']['level_max'])+"\n Time "+ str(maptime_mn)+"'"+ str(maptime_sec)+"\""
else:
scene.objects['Endbanner_points_text']['Text']="Level "+str(scene.objects['Points']['level_max'])+"\n Time "+ str(maptime_mn)+"'"
scene.objects['Endbanner'].color = color_endbanner_bluelight
scene.objects['Endbanner_wave_text'].color = color_black
scene.objects['Endbanner_points_text'].color = color_black
scene.objects['Endbanner_ok_text'].color = color_black
scene.objects['End'].setVisible(True,True)
scene.objects['End']['init_localPosition']= [scene.objects['End'].localPosition.x, scene.objects['End'].localPosition.y, scene.objects['End'].localPosition.z]
pas=0.5
scene.objects['End'].localPosition.y=scene.objects['End'].localPosition.y-1*pas
scene.objects['End'].localPosition.z=scene.objects['End'].localPosition.z+0.85*pas
scene.objects['End']['timer']=scene.objects['End']['timer']+1
if scene.objects['End']['timer']== 40:
scene.objects['Terrain']['map_run'] = False
# Vitesse du jeu
def terrain_speed (obj):
audiodev.play(sndbuff_click)
speed_mode=[0.25, 0.5, 1,2,4,10]
speed_mode_txt=["1/4", "1/2", "1", "2","4","10"]
i=speed_mode.index(scene.objects['Terrain']['speed'])
# Affichage
if obj.name=="Speed_up" and i<5:
scene.objects['Terrain']['speed']=speed_mode[i+1]
scene.objects['Text_speed']['Text']=speed_mode_txt[i+1]
if obj.name=="Speed_down" and i>0:
scene.objects['Terrain']['speed']=speed_mode[i-1]
scene.objects['Text_speed']['Text']=speed_mode_txt[i-1]
# Maj des Nears (Towers) et des Steerings (Minions)
for obj_i in scene.objects:
if "type_tower" in obj_i.getPropertyNames() and "Near" in obj_i.sensors :
obj_i.sensors['Near'].skippedTicks =round(1/(obj_i['speed']*scene.objects['Terrain']['speed']))
if "type_minion" in obj_i.getPropertyNames() and "type_towerminion" not in obj_i.getPropertyNames():
obj_i.actuators['Steering'].velocity=obj_i['speed_base']*scene.objects['Terrain']['speed']
# Configuration du moteur de rendu
if scene.objects['Terrain']['speed']<4: # smaa avec en vitesse 4 et 10 -> tendance au plantage
eevee.use_eevee_smaa = True
else:
eevee.use_eevee_smaa = False
# Maj du fichier de config (vitesse du jeu : data/config/speed)
ct_config_tree[0][0].text=str(scene.objects['Terrain']['speed'])
buffer_xml = ET.tostring(ct_config_tree)
with open("ct_config.xml", "wb") as f:
f.write(buffer_xml)
# Fermer la page de fin
def endbanner_hl(cont):
if cont.sensors['MO'].status == JUST_ACTIVATED:
scene.objects['Endbanner'].color = color_white
scene.objects['Endbanner_wave_text'].color = color_white
scene.objects['Endbanner_points_text'].color = color_white
scene.objects['Endbanner_ok_text'].color = color_white
if cont.sensors['MO'].status == JUST_RELEASED:
scene.objects['Endbanner'].color = color_endbanner_bluelight
scene.objects['Endbanner_wave_text'].color = color_black
scene.objects['Endbanner_points_text'].color = color_black
scene.objects['Endbanner_ok_text'].color = color_black
def endbanner_close(cont):
if cont.sensors['Click'].status == JUST_ACTIVATED and cont.sensors['MO'].positive :
audiodev.play(sndbuff_click)
scene.objects['Terrain']['manip_mode']=0
scene.objects['End'].localPosition.y=scene.objects['End']['init_localPosition'][0]
scene.objects['End'].localPosition.y=scene.objects['End']['init_localPosition'][1]
scene.objects['End'].localPosition.z=scene.objects['End']['init_localPosition'][2]
scene.objects['End'].setVisible(False,True)
scene.objects['Endbanner'].color = [0.592, 0.68, 0.407, 1]
scene.objects['Endbanner_wave_text'].color = color_black
scene.objects['Endbanner_points_text'].color = color_black
scene.objects['End']['timer']= 0
###############################################################################
# Commandes
###############################################################################
# Init
def cmd_init():
# scene.objects['Terrain']['run']=False
scene.objects['Run-Hl'].setVisible(False,False)
scene.objects['Pause'].setVisible(False,False)
scene.objects['Pause-Hl'].setVisible(False,False)
scene.objects['Stop'].setVisible(False,False)
scene.objects['Stop-Hl'].setVisible(False,False)
scene.objects['Construc-Hl'].setVisible(False,False)
scene.objects['Map_text'].setVisible(False,False)
scene.objects['Text_info-1'].setVisible(False,False)
scene.objects['Text_info-2'].setVisible(False,False)
scene.objects['Tower_construc_mode'].setVisible(False,False)
scene.objects['Terrain']['map_run'] = False
# scene.objects['Raz-vue-Hl'].setVisible(False,False)
# scene.objects['Aide-cmd-Hl'].setVisible(False,False)
# Récupérer la config (vitesse du jeu : data/config/speed)
speed_mode=[0.25, 0.5, 1,2,4,10]
speed_mode_txt=["1/4", "1/2", "1", "2","4","10"]
scene.objects['Terrain']['speed']=float(ct_config_tree[0][0].text)
i=speed_mode.index(scene.objects['Terrain']['speed'])
scene.objects['Text_speed']['Text']=speed_mode_txt[i]
# Le highlight des commandes
def cmd_hl(cont):
obj = cont.owner
# Activation
if cont.sensors['MO'].status == JUST_ACTIVATED and scene.objects['Terrain']['manip_mode']==0:
if obj.name!="Run" and obj.name!="Pause" and obj.name!="Run-Hl" and obj.name!="Pause-Hl" and obj.name!="Stop" and obj.name!="Stop-Hl":
obj.setVisible(False,True)
scene.objects[obj.name+'-Hl'].setVisible(True,True)
# Run et pause
if obj.name=="Pause" or obj.name=="Run":
if scene.objects['Terrain']['run'] == True:
scene.objects['Pause'].setVisible(False,False)
scene.objects['Pause-Hl'].setVisible(True,False)
else:
scene.objects['Run'].setVisible(False,False)
scene.objects['Run-Hl'].setVisible(True,False)
# Stop
if obj.name=="Stop":
if scene.objects['Terrain']['thread_run']==True:
scene.objects['Stop'].setVisible(False,False)
scene.objects['Stop-Hl'].setVisible(True,False)
# Désactivation
if cont.sensors['MO'].status == JUST_RELEASED and scene.objects['Terrain']['manip_mode']==0:
if obj.name!="Run" and obj.name!="Pause" and obj.name!="Run-Hl" and obj.name!="Pause-Hl" and obj.name!="Stop" and obj.name!="Stop-Hl":
scene.objects[obj.name+'-Hl'].setVisible(False,True)
obj.setVisible(True,True)
# Run et pause
if obj.name=="Pause" or obj.name=="Run":
if scene.objects['Terrain']['run'] == True:
scene.objects['Pause-Hl'].setVisible(False,False)
scene.objects['Pause'].setVisible(True,False)
else:
scene.objects['Run-Hl'].setVisible(False,False)
scene.objects['Run'].setVisible(True,False)
# Stop
if obj.name=="Stop":
if scene.objects['Terrain']['thread_run']==True:
scene.objects['Stop-Hl'].setVisible(False,False)
scene.objects['Stop'].setVisible(True,False)
# Click sur les commandes
def cmd_click (cont):
obj = cont.owner
if cont.sensors['Click'].status == JUST_ACTIVATED and cont.sensors['MO'].positive and scene.objects['Terrain']['manip_mode']==0:
if obj.name=="Pause" or obj.name=="Run": # FIXME problème sur l'icone pause
terrain_run ()
if obj.name=="Stop":
terrain_stop ()
if obj.name=="Speed_up" or obj.name=="Speed_down":
terrain_speed (obj)
###############################################################################
# Gestion du clavier
###############################################################################
# Mode :
# 0 : rien (par défaut)
# 1 : Pan avec Shift
# 2 : Zoom avec Ctrl
# 9 : Fenêtre modale
def mode(cont):
if scene.objects['Terrain']['manip_mode']==9: # Fenêtre modale
return
obj = cont.owner
keyboard = bge.logic.keyboard
# Shift -> mode 1 : Pan (clic milieu)
if JUST_ACTIVATED in keyboard.inputs[bge.events.LEFTSHIFTKEY].queue:
obj['manip_mode']=1
if JUST_ACTIVATED in keyboard.inputs[bge.events.RIGHTSHIFTKEY].queue:
obj['manip_mode']=1
# Ctrl -> mode 2 : Zoom (clic milieu)
if JUST_ACTIVATED in keyboard.inputs[bge.events.LEFTCTRLKEY].queue:
obj['manip_mode']=2
if JUST_ACTIVATED in keyboard.inputs[bge.events.RIGHTCTRLKEY].queue:
obj['manip_mode']=2
# Pas de modificateur -> mode 0 : Pas de Orbit (mode 0) ici
if JUST_RELEASED in keyboard.inputs[bge.events.LEFTSHIFTKEY].queue:
obj['manip_mode']=0
if JUST_RELEASED in keyboard.inputs[bge.events.RIGHTSHIFTKEY].queue:
obj['manip_mode']=0
if JUST_RELEASED in keyboard.inputs[bge.events.LEFTCTRLKEY].queue:
obj['manip_mode']=0
if JUST_RELEASED in keyboard.inputs[bge.events.RIGHTCTRLKEY].queue:
obj['manip_mode']=0
# Touche Home -> Reset de la vue
if JUST_ACTIVATED in keyboard.inputs[bge.events.HOMEKEY].queue:
scene.objects['Camera'].worldPosition.x = scene.objects['Camera']['init_lx']
scene.objects['Camera'].worldPosition.y = scene.objects['Camera']['init_ly']
scene.objects['Camera'].worldPosition.z = scene.objects['Camera']['init_lz']
applyRotationTo(scene.objects['Terrain'], 0, 0, 0)
# Touche F5 -> Run et Pause
if JUST_ACTIVATED in keyboard.inputs[bge.events.F5KEY].queue:
terrain_run ()
# Touche F6 -> Stop / Init
if JUST_ACTIVATED in keyboard.inputs[bge.events.F6KEY].queue:
if scene.objects['Terrain']['thread_run']==True:
terrain_stop ()
###############################################################################
# Manipulation 3D de la scène
###############################################################################
# Mémorisation de la position et orientation initiales du modèle 3D et de la caméra
def manip_init(cont):
scene.objects['Camera']['init_lx']=scene.objects['Camera'].worldPosition.x
scene.objects['Camera']['init_ly']=scene.objects['Camera'].worldPosition.y
scene.objects['Camera']['init_lz']=scene.objects['Camera'].worldPosition.z
scene.objects['Terrain']['init_lx']=scene.objects['Terrain'].worldPosition.x
scene.objects['Terrain']['init_ly']=scene.objects['Terrain'].worldPosition.y
scene.objects['Terrain']['init_lz']=scene.objects['Terrain'].worldPosition.z
scene.objects['Terrain']['init_rx']=scene.objects['Terrain'].worldOrientation.to_euler().x
scene.objects['Terrain']['init_ry']=scene.objects['Terrain'].worldOrientation.to_euler().y
scene.objects['Terrain']['init_rz']=scene.objects['Terrain'].worldOrientation.to_euler().z
# Atteindre une orientation (bas niveau)
def applyRotationTo(obj, rx=None, ry=None, rz=None, Local=True):
rres=0.001 # resolution rotation
# x
if rx is not None:
while (abs(rx-obj.worldOrientation.to_euler().x) > rres) :
if obj.worldOrientation.to_euler().x-rx > rres:
obj.applyRotation((-rres, 0, 0), Local)
if rx-obj.worldOrientation.to_euler().x > rres:
obj.applyRotation((rres, 0, 0), Local)
# print ("delta x ",rx-obj.worldOrientation.to_euler().x)
# y
if ry is not None:
while (abs(ry-obj.worldOrientation.to_euler().y) > rres) :
if obj.worldOrientation.to_euler().y-ry > rres:
obj.applyRotation((0, -rres, 0), Local)
if ry-obj.worldOrientation.to_euler().y > rres:
obj.applyRotation((0, rres, 0), Local)
# print ("delta y ",ry-obj.worldOrientation.to_euler().y)
# z
if rz is not None:
while (abs(rz-obj.worldOrientation.to_euler().z) > rres) :
if obj.worldOrientation.to_euler().z-rz > rres:
obj.applyRotation((0, 0, -rres), Local)
if rz-obj.worldOrientation.to_euler().z > rres:
obj.applyRotation((0, 0, rres), Local)
# print ("delta z ",rz-obj.worldOrientation.to_euler().z)
# Reset de la manipulation de la vue
def manip_reset(cont):
if cont.sensors['Click'].status == JUST_ACTIVATED and cont.sensors['MO'].positive and scene.objects['Terrain']['manip_mode']==0:
scene.objects['Camera'].worldPosition.x = scene.objects['Camera']['init_lx']
scene.objects['Camera'].worldPosition.y = scene.objects['Camera']['init_ly']
scene.objects['Camera'].worldPosition.z = scene.objects['Camera']['init_lz']
applyRotationTo(scene.objects['Terrain'], 0, 0, 0)
# Position de départ pour la manipulation de la vue
def manip_start(cont):
if scene.objects['Terrain']['manip_mode']!=9:
obj = cont.owner
obj['click_x']=cont.sensors['ClickM'].position[0]
obj['click_y']=cont.sensors['ClickM'].position[1]
# Cacher le cercle de la manipulation Orbit
def manip_stop(cont):
# scene.objects['Orbit'].setVisible(False,False)
pass
# Manipulation du modèle ou de la caméra
def manip(cont):
obj = cont.owner
sensibilite_orbit=0.00005 # Base : 0.0005
sensibilite_pan=0.001 # Base : 0.005
sensibilite_zoom=0.005 # Base : 0.01
delta_x=cont.sensors['DownM'].position[0]-obj['click_x']
delta_y=cont.sensors['DownM'].position[1]-obj['click_y']
# Pan
if obj['manip_mode']==1: # Shift
scene.objects['Camera'].applyMovement((delta_x*-sensibilite_pan, delta_y*sensibilite_pan, 0), True)
# Zoom
if obj['manip_mode']==2: # Ctrl
scene.objects['Camera'].applyMovement((0, 0, (delta_x+delta_y)*sensibilite_zoom), True)
# Manipulation du modèle ou de la caméra
def manip_wheel(cont):
if scene.objects['Terrain']['manip_mode']!=9: # Fenêtre modale
obj = cont.owner
sensibilite_wheel = 5 # Base : 20
if cont.sensors['WheelUp'].positive:
scene.objects['Camera'].applyMovement((0, 0, -sensibilite_wheel), True)
if cont.sensors['WheelDown'].positive:
scene.objects['Camera'].applyMovement((0, 0, sensibilite_wheel), True)
###############################################################################
# Aide
###############################################################################
# # Ouvrir la page d'aide
# def aide(cont):
# if cont.sensors['Click'].status == JUST_ACTIVATED and cont.sensors['MO'].positive :
# # scene.replace('Scene-Aide') # Bug Eevee -> même scene mais camera différente
# scene.active_camera=scene.objects['Aide-Camera']
# scene.objects['Apropos-Lien_projet'].color= color_link
# scene.objects['Apropos-Lien_maquette'].color= color_link
# scene.objects['Apropos-Lien_a4'].color= color_link
# scene.objects['Apropos-Lien_blender'].color= color_link
# scene.objects['Apropos-Lien_upbge'].color= color_link
# scene.objects['Apropos-Lien_cc'].color= color_link
# scene.objects['Apropos-Lien_gpl'].color= color_link
# # Fermer la page d'aide
# def aide_fermer(cont):
# if cont.sensors['Click'].status == JUST_ACTIVATED and cont.sensors['MO'].positive :
# # bge.logic.addScene('Scene') # Bug Eevee -> même scene mais camera différente
# scene.active_camera=scene.objects['Camera']
# # Aller sur les liens
# def aide_apropos(cont):
# if cont.sensors['Click'].status == JUST_ACTIVATED and cont.sensors['MO'].positive :
# obj = cont.owner
# if obj.name == "Apropos-Lien_maquette" :
# webbrowser.open('https://www.a4.fr/wiki/')
# if obj.name == "Apropos-Lien_projet" :
# webbrowser.open('https://gitlab.com/blender-edutech')
# if obj.name == "Apropos-Lien_a4" :
# webbrowser.open('https://www.a4.fr')
# if obj.name == "Apropos-Lien_blender" :
# webbrowser.open('https://blender.org')
# if obj.name == "Apropos-Lien_upbge" :
# webbrowser.open('https://upbge.org')
# if obj.name == "Apropos-Lien_cc" :
# webbrowser.open('https://creativecommons.org/licenses/by-sa/4.0/')
# if obj.name == "Apropos-Lien_gpl" :
# webbrowser.open('https://www.gnu.org/licenses/gpl-3.0.html')
# # Le highlight des liens
# def aide_apropos_hl(cont):
# if cont.sensors['MO'].status == JUST_ACTIVATED :
# obj = cont.owner
# obj.color = color_link_hl
# if cont.sensors['MO'].status == JUST_RELEASED :
# obj = cont.owner
# obj.color = color_link