diff --git a/ursina/collider.py b/ursina/collider.py index 85767a19..05831610 100644 --- a/ursina/collider.py +++ b/ursina/collider.py @@ -5,9 +5,18 @@ class Collider(NodePath): - def __init__(self): - super().__init__('box_collider') - self._visible = False + def __init__(self, entity, shape): + super().__init__('collider') + self.collision_node = CollisionNode('CollisionNode') + + self.shape = shape + self.node_path = entity.attachNewNode(self.collision_node) + + if isinstance(shape, (list, tuple)): + for e in shape: + self.node_path.node().addSolid(e) + else: + self.node_path.node().addSolid(self.shape) def remove(self): @@ -32,30 +41,19 @@ def visible(self, value): class BoxCollider(Collider): def __init__(self, entity, center=(0,0,0), size=(1,1,1)): - super().__init__() self.center = center self.size = size size = [e/2 for e in size] size = [max(0.001, e) for e in size] # collider needs to have thickness - self.shape = CollisionBox(Vec3(center[0], center[1], center[2]), size[0], size[1], size[2]) - # self.remove() - self.collision_node = CollisionNode('CollisionNode') - self.node_path = entity.attachNewNode(self.collision_node) - self.node_path.node().addSolid(self.shape) - self.visible = False - # self.node_path.show() - # for some reason self.node_path gets removed after this and can't be shown. + super().__init__(entity, CollisionBox(Vec3(center[0], center[1], center[2]), size[0], size[1], size[2])) + class SphereCollider(Collider): def __init__(self, entity, center=(0,0,0), radius=.5): self.center = center self.radius = radius - super().__init__() - self.shape = CollisionSphere(center[0], center[1], center[2], radius) - self.node_path = entity.attachNewNode(CollisionNode('CollisionNode')) - self.node_path.node().addSolid(self.shape) - self.visible = False + super().__init__(entity, CollisionSphere(center[0], center[1], center[2], radius)) class CapsuleCollider(Collider): @@ -63,23 +61,17 @@ def __init__(self, entity, center=(0,0,0), height=2, radius=.5): self.center = center self.height = height self.radius = radius - super().__init__() - self.shape = CollisionCapsule(center[0], center[1] + radius, center[2], center[0], center[1] + height, center[2], radius) - self.node_path = entity.attachNewNode(CollisionNode('CollisionNode')) - self.node_path.node().addSolid(self.shape) - self.visible = False + super().__init__(entity, CollisionCapsule(center[0], center[1] + radius, center[2], center[0], center[1] + height, center[2], radius)) class MeshCollider(Collider): def __init__(self, entity, mesh=None, center=(0,0,0)): self.center = center - super().__init__() center = Vec3(center) if mesh is None and entity.model: mesh = entity.model # print('''auto generating mesh collider from entity's mesh''') - self.node_path = entity.attachNewNode(CollisionNode('CollisionNode')) self.collision_polygons = [] if isinstance(mesh, Mesh): @@ -132,11 +124,7 @@ def __init__(self, entity, mesh=None, center=(0,0,0)): p = CollisionPolygon(Vec3(verts[i+2]), Vec3(verts[i+1]), Vec3(verts[i])) self.collision_polygons.append(p) - - node = self.node_path.node() - for poly in self.collision_polygons: - node.addSolid(poly) - self.visible = False + super().__init__(entity, self.collision_polygons) def remove(self): @@ -145,6 +133,7 @@ def remove(self): self.node_path.removeNode() + if __name__ == '__main__': from ursina import * from ursina import Ursina, Entity, Pipe, Circle, Button, scene, EditorCamera, color diff --git a/ursina/entity.py b/ursina/entity.py index 9cf34ece..e1690b89 100644 --- a/ursina/entity.py +++ b/ursina/entity.py @@ -709,6 +709,14 @@ def set_shader_input(self, name, value): super().set_shader_input(name, value) + def shader_input_getter(self): + return self._shader_inputs + + def shader_input_setter(self, value): + for key, value in value.items(): + self.set_shader_input(key, value) + + def texture_getter(self): return getattr(self, '_texture', None) @@ -804,6 +812,10 @@ def unlit_getter(self): def unlit_setter(self, value): self._unlit = value self.setLightOff(value) + if value: + self.hide(0b0001) + else: + self.show(0b0001) def billboard_getter(self): # set to True to make this Entity always face the camera. return getattr(self, '_billboard', False) diff --git a/ursina/input_handler.py b/ursina/input_handler.py index 3a015de6..50fec868 100644 --- a/ursina/input_handler.py +++ b/ursina/input_handler.py @@ -93,14 +93,29 @@ def __eq__(self, other): def bind(original_key, alternative_key): - rebinds[original_key] = alternative_key + if not original_key in rebinds: + rebinds[original_key] = {original_key, } + + rebinds[original_key].add(alternative_key) + + if ' mouse ' in alternative_key: - rebinds[original_key + ' up'] = alternative_key[:-5] + ' up' + if not rebinds.get(f'{original_key} up'): + rebinds[f'{original_key} up'] = {original_key, } + rebinds[f'{original_key} up'].add(f'{alternative_key[:-5]} up') return - rebinds[original_key + ' hold'] = alternative_key + ' hold' - rebinds[original_key + ' up'] = alternative_key + ' up' + if not rebinds.get(f'{original_key} hold'): + rebinds[f'{original_key} hold'] = {f'{original_key} hold', } + rebinds[f'{original_key} hold'].add(f'{alternative_key} hold') + + if not rebinds.get(f'{original_key} up'): + rebinds[f'{original_key} up'] = {f'{original_key} up', } + rebinds[f'{original_key} up'].add(f'{alternative_key} up') + + # rebinds[original_key + ' hold'] = alternative_key + ' hold' + # rebinds[original_key + ' up'] = alternative_key + ' up' def unbind(key): if key in rebinds: @@ -144,25 +159,27 @@ def get_combined_key(key): from ursina import * from ursina import Ursina, input_handler - app = Ursina() - input_handler.rebind('z', 'w') # 'z'-key will now be registered as 'w'-key + app = Ursina(borderless=False) + input_handler.bind('z', 'w') # 'z'-key will now be registered as 'w'-key + input_handler.bind('left mouse down', 'attack') # 'left mouse down'-key will now send 'attack'to input functions + input_handler.bind('gamepad b', 'attack') # 'gamepad b'-key will now be registered as 'attack'-key + - def test(): - print('----') - # input_handler.rebind('a', 'f') def input(key): - print(key) - if key == 'left mouse down': - print('pressed left mouse button') + print('got key:', key) + if key == 'attack': + destroy(Entity(model='cube', color=color.blue), delay=.2) + # if key == 'left mouse down': + # print('pressed left mouse button') - if key == Keys.left_mouse_down: # same as above, but with Keys enum. - print('pressed left mouse button') + # if key == Keys.left_mouse_down: # same as above, but with Keys enum. + # print('pressed left mouse button') - def update(): - for key, value in held_keys.items(): - if value != 0: - print(key, value) + # def update(): + # for key, value in held_keys.items(): + # if value != 0: + # print(key, value) diff --git a/ursina/main.py b/ursina/main.py index 9a0aa5f4..c6c6445a 100644 --- a/ursina/main.py +++ b/ursina/main.py @@ -233,36 +233,49 @@ def input(self, key, is_raw=False): # internal method for handling input if key in self._input_name_changes: key = self._input_name_changes[key] - if key in input_handler.rebinds: - key = input_handler.rebinds[key] + # since we can rebind one key to multiple, get a list of keys + bound_keys = input_handler.rebinds.get(key, (key, )) - input_handler.input(key) + for key in bound_keys: + input_handler.input(key) if not application.paused: if hasattr(__main__, 'input'): - __main__.input(key) - - for e in scene.entities: - if e.enabled is False or e.ignore or e.ignore_input: - continue - if application.paused and e.ignore_paused is False: - continue - if e.has_disabled_ancestor(): - continue + for key in bound_keys: + __main__.input(key) + + for e in scene.entities: + if e.enabled is False or e.ignore or e.ignore_input: + continue + if application.paused and e.ignore_paused is False: + continue + if e.has_disabled_ancestor(): + continue + + + if hasattr(e, 'input') and callable(e.input): + break_outer = False + for key in bound_keys: + if break_outer: + break + if e.input(key): # if the input function returns True, eat the input + break_outer = True - if hasattr(e, 'input') and callable(e.input): - if e.input(key): - break + if hasattr(e, 'scripts'): + break_outer = False + if break_outer: + break - if hasattr(e, 'scripts'): - for script in e.scripts: - if script.enabled and hasattr(script, 'input') and callable(script.input): - if script.input(key): - break + for script in e.scripts: + if script.enabled and hasattr(script, 'input') and callable(script.input): + for key in bound_keys: + if script.input(key): # if the input function returns True, eat the input + break_outer = True + break - mouse.input(key) + mouse.input(key) def text_input(self, key): # internal method for handling text input diff --git a/ursina/mesh.py b/ursina/mesh.py index 6b767c77..44e4b825 100644 --- a/ursina/mesh.py +++ b/ursina/mesh.py @@ -85,8 +85,10 @@ def _set_array_data(self, array_handle, data, dtype_string='f'): a = array.array(dtype_string, data) vmem = memoryview(array_handle).cast('B').cast(dtype_string) - vmem[:] = a - + try: + vmem[:] = a + except: + raise Exception(f'Error in Mesh. Ensure Mesh is valid and the inputs have same length: vertices:{len(self.vertices)}, triangles:{len(self.triangles)}, normals:{len(self.normals)}, colors:{len(self.colors)}, uvs:{len(self.uvs)}') def generate(self): self._generated_vertices = None diff --git a/ursina/prefabs/file_browser_save.py b/ursina/prefabs/file_browser_save.py index cef3d688..547747e5 100644 --- a/ursina/prefabs/file_browser_save.py +++ b/ursina/prefabs/file_browser_save.py @@ -2,6 +2,7 @@ from ursina.prefabs.file_browser import FileBrowser +@generate_properties_for_class() class FileBrowserSave(FileBrowser): def __init__(self, **kwargs): super().__init__() @@ -9,38 +10,30 @@ def __init__(self, **kwargs): self.save_button = self.open_button self.save_button.color = color.azure self.save_button.text = 'Save' - self.save_button.on_click = self.save - self.file_name_field = InputField( - parent = self, - scale_x = .75, - scale_y = self.save_button.scale_y, - y = self.save_button.y, - active = True, - ) - # self.file_name_field.text_field.scale *= .6 + self.save_button.on_click = self._save + self.file_name_field = InputField(parent=self, scale_x=.75, scale_y=self.save_button.scale_y, y=self.save_button.y, active=True) self.save_button.y -= .075 self.cancel_button.y -= .075 self.file_name_field.text_field.text = '' self.file_type = '' # to save as - self.data = '' self.last_saved_file = None # gets set when you save a file self.overwrite_prompt = WindowPanel( content=( Text('Overwrite?'), - Button('Yes', color=color.azure, on_click=self.save), + Button('Yes', color=color.azure, on_click=self._save), Button('Cancel') - ), - z=-1, - popup=True, - enabled=False) + ), z=-1, popup=True, enabled=False) for key, value in kwargs.items(): setattr(self, key ,value) + def file_type_setter(self, value): + self.file_types = (value, ) - def save(self): + + def _save(self): file_name = self.file_name_field.text_field.text if not file_name.endswith(self.file_type): file_name += self.file_type @@ -51,17 +44,14 @@ def save(self): # print('overwrite file?') self.overwrite_prompt.enabled = True - else: - if isinstance(self.data, str): - path.write_text(self.data) - else: - # print('write bytes') - path.write_bytes(self.data) + self.last_saved_file = path + self.overwrite_prompt.enabled = False + self.close() + self.on_submit(path) - self.last_saved_file = path - self.overwrite_prompt.enabled = False - self.close() + def on_submit(self, path): # implement .on_submit to handle saving + print('save to path:', path, 'please implement .on_submit to handle saving') diff --git a/ursina/prefabs/pause_menu.py b/ursina/prefabs/pause_menu.py new file mode 100644 index 00000000..8d7b9924 --- /dev/null +++ b/ursina/prefabs/pause_menu.py @@ -0,0 +1,33 @@ +from ursina import * + + +class PauseMenu(Entity): + def __init__(self, **kwargs): + super().__init__(ignore_paused=True, **kwargs) + self.menu = Entity(parent=camera.ui, enabled=False) + self.bg = Entity(parent=self.menu, model='quad', color=color.black, alpha=.5, scale=3) + self.pause_text = Text(parent=self.menu, text='PAUSED', origin=(0,0), scale=2) + self.lock_mouse_on_resume = False + + def on_destroy(self): + destroy(self.menu) + + def input(self, key): + if key == 'escape': + if not application.paused: + self.lock_mouse_on_resume = mouse.locked + mouse.locked = False + else: + mouse.locked = self.lock_mouse_on_resume + + application.paused = not application.paused # Pause/unpause the game. + self.menu.enabled = application.paused # Also toggle "PAUSED" graphic. + + + +if __name__ == '__main__': + app = Ursina() + + PauseMenu() + + app.run() diff --git a/ursina/scripts/_blend_export.py b/ursina/scripts/_blend_export.py index 845bf823..6257a9bc 100644 --- a/ursina/scripts/_blend_export.py +++ b/ursina/scripts/_blend_export.py @@ -6,29 +6,21 @@ blend_file_path = bpy.data.filepath filepath = sys.argv[-1] - -bpy.ops.export_scene.obj( +bpy.ops.wm.obj_export( filepath=filepath, check_existing=True, - axis_forward='Z', - axis_up='Y', + forward_axis='Z', + up_axis='Y', filter_glob="*.obj;*.mtl", - use_selection=False, - use_animation=False, - use_mesh_modifiers=True, - use_edges=False, - use_smooth_groups=False, - use_smooth_groups_bitflags=False, - use_normals=True, - use_uvs=True, - use_materials=False, - use_triangles=True, - use_nurbs=False, - use_vertex_groups=False, - use_blen_objects=True, - group_by_object=False, - group_by_material=False, - keep_vertex_order=False, + export_selected_objects=False, + export_smooth_groups=False, + export_normals=True, + export_uv=True, + export_materials=True, + export_colors=True, + export_triangulated_mesh=True, + export_curves_as_nurbs=False, + export_vertex_groups=False, global_scale=1, path_mode='AUTO' ) diff --git a/ursina/shaders/matcap_shader.py b/ursina/shaders/matcap_shader.py index 5196d448..a799bf99 100644 --- a/ursina/shaders/matcap_shader.py +++ b/ursina/shaders/matcap_shader.py @@ -28,14 +28,11 @@ out vec4 fragColor; void main() { - - vec3 r = reflect( eye, view_normal ); - float m = 2. * sqrt( pow( r.x, 2. ) + pow( r.y, 2. ) + pow( r.z + 1., 2. ) ); + vec3 r = reflect(eye, view_normal); + float m = 2. * sqrt(pow(r.x, 2.) + pow(r.y, 2.) + pow(r.z + 1., 2.)); vec2 vN = r.xy / m + .5; - vec3 base = texture2D( p3d_Texture0, vN ).rgb; - // vec3 base = texture2D( p3d_Texture0, uv ).rgb; - fragColor = vec4( base, 1. ) * p3d_ColorScale; + fragColor = texture(p3d_Texture0, vN) * p3d_ColorScale; } ''', diff --git a/ursina/shaders/triplanar_shader.py b/ursina/shaders/triplanar_shader.py index ee404557..5a296caf 100644 --- a/ursina/shaders/triplanar_shader.py +++ b/ursina/shaders/triplanar_shader.py @@ -56,34 +56,29 @@ return blend_weights*rcpBlend; } -//Constant width Triplanar blending -vec3 TriPlanarBlendWeightsConstantOverlap(vec3 normal) { +void main() { + // vec3 blendFast = TriPlanarBlendWeightsConstantOverlap(world_normal); - vec3 blend_weights = normal*normal;//or abs(normal) for linear falloff(and adjust BlendZone) - float maxBlend = max(blend_weights.x, max(blend_weights.y, blend_weights.z)); + //Constant width Triplanar blending + vec3 blend_weights = world_normal * world_normal;//or abs(world_normal) for linear falloff(and adjust BlendZone) + float maxBlend = max(blend_weights.x, max(blend_weights.y, blend_weights.z)); float BlendZone = 0.8f; - blend_weights = blend_weights - maxBlend*BlendZone; - - blend_weights = max(blend_weights, 0.0); - - float rcpBlend = 1.0 / (blend_weights.x + blend_weights.y + blend_weights.z); - return blend_weights*rcpBlend; -} + blend_weights = blend_weights - maxBlend*BlendZone; + blend_weights = max(blend_weights, 0.0); -void main(){ - vec3 blendFast = TriPlanarBlendWeightsConstantOverlap(world_normal); - vec3 blend = blendFast; + float rcpBlend = 1.0 / (blend_weights.x + blend_weights.y + blend_weights.z); + vec3 blend = blend_weights * rcpBlend; vec3 albedoX = texture(side_texture, vertex_world_position.zy * side_texture_scale).rgb*blend.x; - vec3 albedoY = texture(side_texture, vertex_world_position.xz * side_texture_scale).rgb*blend.y; - vec3 albedoZ = texture(side_texture, vertex_world_position.xy * side_texture_scale).rgb*blend.z; + vec3 albedoY = texture(side_texture, vertex_world_position.xz * side_texture_scale).rgb*blend.y; + vec3 albedoZ = texture(side_texture, vertex_world_position.xy * side_texture_scale).rgb*blend.z; if (world_normal.y > .0) { - albedoY = texture(p3d_Texture0, vertex_world_position.xz * texture_scale.xy).rgb*blend.y; + albedoY = texture(p3d_Texture0, vertex_world_position.xz * texture_scale.xy).rgb*blend.y; } - vec3 triPlanar = (albedoX + albedoY + albedoZ); + vec3 triPlanar = (albedoX + albedoY + albedoZ); fragColor = vec4(triPlanar.rgb, 1) * vertex_color; } diff --git a/ursina/vec3.py b/ursina/vec3.py index b2d0fa12..6b8ce693 100644 --- a/ursina/vec3.py +++ b/ursina/vec3.py @@ -111,7 +111,7 @@ def __mul__(self, value): if isinstance(value, (int, float, complex)): return Vec3(*(e*value for e in self)) - return Vec3(self[0]*value[0], self[1]*value[1], self[2]*value[2]) + return Vec3(self[0]*value[0], self[1]*value[1], self[2]* (value[2] if len(value) > 2 else 1)) __rmul__ = __mul__ @@ -153,3 +153,5 @@ def __abs__(self): print('-----------', a * 2) print('-----------', 2 * a) print(abs(Vec3(-1,2,-3))) + + print(Vec3(1,1,1) * (2,2)) diff --git a/ursina/window.py b/ursina/window.py index 4081e8b4..d528bacd 100644 --- a/ursina/window.py +++ b/ursina/window.py @@ -158,12 +158,12 @@ def _exit_button_input(key): self.exit_button.on_click() self.exit_button.input = _exit_button_input - self.fps_counter = Text(parent=self.editor_ui, eternal=True, text='60', ignore=False, i=0, + self.fps_counter = Text(parent=self.editor_ui, eternal=True, text='60', ignore=False, i=0, ignore_paused=True, position=((.5*self.aspect_ratio)-self.exit_button.scale_x, .47+(.02*(not self.exit_button.enabled)), -999)) def _fps_counter_update(): if self.fps_counter.i > 60: - self.fps_counter.text = str(int(1//time.dt)) + self.fps_counter.text = str(int(1//time.dt_unscaled)) self.fps_counter.i = 0 self.fps_counter.i += 1 self.fps_counter.update = _fps_counter_update