Skip to content

Commit

Permalink
Merge branch 'pokepetter:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
PaologGithub authored Oct 26, 2024
2 parents 5020278 + 7f107a7 commit 272a7e3
Show file tree
Hide file tree
Showing 19 changed files with 691 additions and 424 deletions.
1 change: 1 addition & 0 deletions ursina/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from panda3d.core import Mat3, Mat4
from ursina.ursinamath import *
from ursina.ursinastuff import *
from ursina.array_tools import *
from ursina import input_handler
from ursina.input_handler import held_keys, Keys
from ursina.string_utilities import *
Expand Down
165 changes: 165 additions & 0 deletions ursina/array_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
def _test(function, test_input, expected_result):
result = function(test_input)
if result == expected_result:
print('PASSED:', function.__name__)
else:
print('FAILED:', function.__name__)
print('result:', result)
print('expected result:', expected_result)

from typing import List
class Array2D(list):
__slots__ = ('width', 'height', 'default_value')

def __init__(self, width:int=None, height:int=None, default_value=0, data:List[List]=None):
self.default_value = default_value

if data is not None: # initialize with provided data
self.width = len(data)
self.height = len(data[0]) if self.width > 0 else 0
if any(len(row) != self.height for row in data):
raise ValueError("All rows in the data must have the same length.")
super().__init__(data)

else: # Initialize with default values
if width is None or height is None:
raise ValueError("Width and height must be provided if no data is given.")

self.width = width
self.height = height
super().__init__([[self.default_value for _ in range(self.height)] for _ in range(self.width)])

def to_string(self):
lines = []
for y in range(self.height-1, -1, -1):
line = ', '.join([str(self[x][y]) for x in range(self.width)])
lines.append(line)
return '\n'.join(lines)

def __str__(self):
lines = self.to_string().strip().split('\n')
longest_number = int(len(str(self.height)))
for y in range(self.height-1, -1, -1):
lines[y] = f'{self.height-1-y:<{longest_number}}| {lines[y]}'

lines.append(f'{'o':<{longest_number}}{'-'*self.width}')
result = '\n'.join(lines)
return result


def reset(self):
for x in range(self.width):
for y in range(self.height):
self[x][y] = self.default_value


if __name__ == '__main__':
grid = Array2D(width=16, height=8)
print(grid)



class Array3D(list):
__slots__ = ('width', 'height', 'depth', 'default_value', 'data')

def __init__(self, width:int, height:int, depth:int, default_value=0):
self.width = int(width)
self.height = int(height)
self.depth = int(depth)
self.default_value = default_value
super().__init__([Array2D(self.height, self.depth) for x in range(self.width)])

def reset(self):
for x in range(self.width):
for y in range(self.height):
for z in range(self.depth):
self[x][y][z] = self.default_value


def chunk_list(target_list, chunk_size):
# yield successive chunks from list
for i in range(0, len(target_list), chunk_size):
yield target_list[i:i + chunk_size]


def flatten_list(target_list):
import itertools
return list(itertools.chain(*target_list))


def flatten_completely(target_list):
for i in target_list:
if isinstance(i, (tuple, list)):
for j in flatten_list(i):
yield j
else:
yield i


def enumerate_2d(target_2d_list): # usage: for (x, y), value in enumerate_2d(my_2d_list)
for x, line in enumerate(target_2d_list):
for y, value in enumerate(line):
yield (x, y), value


def enumerate_3d(target_3d_list): # usage: for (x, y, z), value in enumerate_3d(my_3d_list)
for x, vertical_slice in enumerate(target_3d_list):
for y, log in enumerate(vertical_slice):
for z, value in enumerate(log):
yield (x, y, z), value


def rotate_2d_list(target_2d_list):
return list(zip(*target_2d_list[::-1])) # rotate


def string_to_2d_list(string, char_value_map={'.':0, '#':1}):
from textwrap import dedent
grid = dedent(string).strip()
grid = grid.split('\n')
grid = [[char_value_map.get(e, 0) for e in line] for line in grid]
grid = list(zip(*grid[::-1])) # rotate
return grid

if __name__ == '__main__':
test_input = '''
#..#.###..####..#..
#..#.#..#.###...#..
#..#.###.....#..#..
.##..#..#.####..#..
'''
expected_result = rotate_2d_list([
(1,0,0,1, 0, 1,1,1,0, 0, 1,1,1,1, 0, 0,1,0,0),
(1,0,0,1, 0, 1,0,0,1, 0, 1,1,1,0, 0, 0,1,0,0),
(1,0,0,1, 0, 1,1,1,0, 0, 0,0,0,1, 0, 0,1,0,0),
(0,1,1,0, 0, 1,0,0,1, 0, 1,1,1,1, 0, 0,1,0,0),
])

_test(string_to_2d_list, test_input, expected_result)


def list_2d_to_string(target_2d_list, characters='.#'):
return '\n'.join([''.join([characters[e] for e in line]) for line in target_2d_list])

if __name__ == '__main__':
list_2d = [
[1,0,0,1, 0, 1,1,1,0, 0, 1,1,1,1, 0, 0,1,0,0],
[1,0,0,1, 0, 1,0,0,1, 0, 1,1,1,0, 0, 0,1,0,0],
[1,0,0,1, 0, 1,1,1,0, 0, 0,0,0,1, 0, 0,1,0,0],
[0,1,1,0, 0, 1,0,0,1, 0, 1,1,1,1, 0, 0,1,0,0],
]
from textwrap import dedent
expected_result = dedent('''
#..#.###..####..#..
#..#.#..#.###...#..
#..#.###.....#..#..
.##..#..#.####..#..
''').strip()

_test(list_2d_to_string, list_2d, expected_result)


class LoopingList(list):
def __getitem__(self, i):
return super().__getitem__(i % len(self))

14 changes: 6 additions & 8 deletions ursina/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,25 +138,23 @@ def stop(self, destroy=True):
if destroy:
_destroy(self)

def fade(self, value, duration=.5, delay=0, curve=curve.in_expo, resolution=None, interrupt=True):
self.animate('volume', value=value, duration=duration, delay=delay, curve=curve, resolution=resolution, interrupt=interrupt)
def fade(self, value, duration=.5, delay=0, curve=curve.in_expo, resolution=None, interrupt=True, ignore_paused=True):
self.animate('volume', value=value, duration=duration, delay=delay, curve=curve, resolution=resolution, interrupt=interrupt, ignore_paused=ignore_paused)

def fade_in(self, value=1, duration=.5, delay=0, curve=curve.in_expo, resolution=None, interrupt='finish',
destroy_on_ended=False):
def fade_in(self, value=1, duration=.5, delay=0, curve=curve.in_expo, resolution=None, interrupt='finish', destroy_on_ended=False, ignore_paused=True):
if duration+delay <= 0:
self.volume = value
else:
self.animate('volume', value, duration=duration, delay=delay, curve=curve, resolution=resolution, interrupt=interrupt)
self.animate('volume', value, duration=duration, delay=delay, curve=curve, resolution=resolution, interrupt=interrupt, ignore_paused=ignore_paused)
if destroy_on_ended:
_destroy(self, delay=delay+duration + .01)

def fade_out(self, value=0, duration=.5, delay=0, curve=curve.in_expo, resolution=None, interrupt='finish',
destroy_on_ended=True):
def fade_out(self, value=0, duration=.5, delay=0, curve=curve.in_expo, resolution=None, interrupt='finish', destroy_on_ended=True, ignore_paused=True):

if duration+delay <= 0:
self.volume = value
else:
self.animate('volume', value, duration=duration, delay=delay, curve=curve, resolution=resolution, interrupt=interrupt)
self.animate('volume', value, duration=duration, delay=delay, curve=curve, resolution=resolution, interrupt=interrupt, ignore_paused=ignore_paused)
if destroy_on_ended:
_destroy(self, delay=delay+duration + .05)

Expand Down
42 changes: 29 additions & 13 deletions ursina/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@ def __init__(self, add_to_scene_entities=True, enabled=True, **kwargs):
scene.entities.append(self)

self._shader_inputs = {}
if Entity.default_shader:
self.shader = Entity.default_shader

self.setPythonTag('Entity', self) # for the raycast to get the Entity and not just the NodePath
self.scripts = [] # add with add_script(class_instance). will assign an 'entity' variable to the script.
self.animations = []
Expand Down Expand Up @@ -677,12 +674,12 @@ def shader_setter(self, value):
return

if value is None:
self.model.setShaderAuto()
self.setShaderAuto()
return

if isinstance(value, Panda3dShader): # panda3d shader
self._shader = value
self.model.setShader(value)
self.setShader(value)
return

if isinstance(value, str):
Expand All @@ -697,7 +694,7 @@ def shader_setter(self, value):
if not value.compiled:
value.compile()

self.model.setShader(value._shader)
self.setShader(value._shader)
value.entity = self

for key, value in value.default_input.items():
Expand Down Expand Up @@ -1082,13 +1079,20 @@ def __deepcopy__(self, memo):
#------------
# ANIMATIONS
#------------
def animate(self, name, value, duration=.1, delay=0, curve=curve.in_expo, loop=False, resolution=None, interrupt='kill', time_step=None, unscaled=False, auto_play=True, auto_destroy=True):

def _getattr(self, name):
return getattr(self, name)

def _setattr(self, name, value):
setattr(self, name, value)

def animate(self, name, value, duration=.1, delay=0, curve=curve.in_expo, loop=False, resolution=None, interrupt='kill', time_step=None, unscaled=False, ignore_paused=None, auto_play=True, auto_destroy=True, getattr_function=None, setattr_function=None):
if duration == 0 and delay == 0:
setattr(self, name, value)
return None

if self.ignore_paused:
unscaled = True
if ignore_paused is None: # if ignore_pause is not specified, inherit it from the entity
ignore_paused = self.ignore_paused

animator_name = name + '_animator'
# print('start animating value:', name, animator_name )
Expand All @@ -1098,7 +1102,7 @@ def animate(self, name, value, duration=.1, delay=0, curve=curve.in_expo, loop=F
if hasattr(self, animator_name) and getattr(self, animator_name) in self.animations:
self.animations.remove(getattr(self, animator_name))

sequence = Sequence(loop=loop, time_step=time_step, auto_destroy=auto_destroy, unscaled=unscaled, ignore_paused=self.ignore_paused, name=name)
sequence = Sequence(loop=loop, time_step=time_step, auto_destroy=auto_destroy, unscaled=unscaled, ignore_paused=ignore_paused, name=name)
sequence.append(Wait(delay))

setattr(self, animator_name, sequence)
Expand All @@ -1107,12 +1111,18 @@ def animate(self, name, value, duration=.1, delay=0, curve=curve.in_expo, loop=F
if not resolution:
resolution = max(int(duration * 60), 1)

# if no custom getattr and setattr functions are provided (for example when using animate_shader_input), animate the entity's variable.
if not getattr_function:
getattr_function = self._getattr
if not setattr_function:
setattr_function = self._setattr

for i in range(resolution+1):
t = i / resolution
t = curve(t)

sequence.append(Wait(duration / resolution))
sequence.append(Func(setattr, self, name, lerp(getattr(self, name), value, t)))
sequence.append(Func(setattr_function, name, lerp(getattr_function(name), value, t)))

if auto_play:
sequence.start()
Expand Down Expand Up @@ -1140,6 +1150,12 @@ def animate_scale(self, value, duration=.1, **kwargs):

return self.animate('scale', value, duration=duration, **kwargs)


def animate_shader_input(self, name, value, **kwargs):
# instead of settings entity variables, set shader input
self.animate(name, value, getattr_function=self.get_shader_input, setattr_function=self.set_shader_input, **kwargs)


# generate animation functions
for e in ('x', 'y', 'z', 'rotation_x', 'rotation_y', 'rotation_z', 'scale_x', 'scale_y', 'scale_z'):
exec(dedent(f'''
Expand All @@ -1148,7 +1164,7 @@ def animate_{e}(self, value, duration=.1, delay=0, unscaled=False, **kwargs):
'''))


def shake(self, duration=.2, magnitude=1, speed=.05, direction=(1,1), delay=0, attr_name='position', interrupt='finish', unscaled=False):
def shake(self, duration=.2, magnitude=1, speed=.05, direction=(1,1), delay=0, attr_name='position', interrupt='finish', unscaled=False, ignore_paused=True):
import random

if hasattr(self, 'shake_sequence') and self.shake_sequence:
Expand All @@ -1169,7 +1185,7 @@ def shake(self, duration=.2, magnitude=1, speed=.05, direction=(1,1), delay=0, a

self.animations.append(self.shake_sequence)
self.shake_sequence.unscaled = unscaled
self.shake_sequence.ignore_paused = self.ignore_paused
self.shake_sequence.ignore_paused = ignore_paused
self.shake_sequence.start()
return self.shake_sequence

Expand Down
2 changes: 1 addition & 1 deletion ursina/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ursina.scripts.generate_normals import generate_normals
from ursina.scripts.project_uvs import project_uvs
from ursina.scripts.colorize import colorize
from ursina.ursinastuff import LoopingList
from ursina.array_tools import LoopingList
from ursina.vec3 import Vec3
from ursina.vec2 import Vec2
from ursina.sequence import Func
Expand Down
9 changes: 6 additions & 3 deletions ursina/music_system.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ursina.audio import Audio
from ursina.ursinastuff import invoke
from ursina import curve
from ursina.string_utilities import print_warning


tracks = dict()
Expand All @@ -20,7 +21,7 @@ def play(track_name, fade_out_duration=2):
if track_name not in tracks:
audio_instance = Audio(track_name, loop=True, autoplay=False, group='music')
if not audio_instance.clip:
print_warning('music track not found:', value)
print_warning('music track not found:', track_name)
return
tracks[track_name] = audio_instance

Expand All @@ -30,9 +31,11 @@ def play(track_name, fade_out_duration=2):
return

# fade out current track and play new one after
tracks[current_track].fade_out(duration=fade_out_duration, curve=curve.linear)
print('music_system: fade out:', current_track)
tracks[current_track].fade_out(duration=fade_out_duration, curve=curve.linear, destroy_on_ended=False, ignore_paused=True)
tracks.pop(current_track, None)
invoke(tracks[track_name].play, delay=fade_out_duration)
print('music_system: fade in:', track_name)
invoke(tracks[track_name].play, delay=fade_out_duration, ignore_paused=True)
current_track = track_name


Expand Down
Loading

0 comments on commit 272a7e3

Please sign in to comment.