Legend of the Gold Box... A game written for the LOWREZJAM 2018 game jam
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

417 lines
13KB

  1. '''
  2. Filename nodes.py
  3. Author: Bryan "ObsidianBlk" Miller
  4. Date Created: 8/1/2018
  5. Python Version: 3.7
  6. '''
  7. from .display import Display
  8. from .events import Events
  9. import pygame
  10. class NodeError(Exception):
  11. pass
  12. class Node:
  13. def __init__(self, name="Node", parent=None):
  14. self._parent = None
  15. self._name = name
  16. self._children = []
  17. if parent is not None:
  18. try:
  19. self.parent = parent
  20. except NodeError as e:
  21. raise e
  22. @property
  23. def parent(self):
  24. return self._parent
  25. @parent.setter
  26. def parent(self, new_parent):
  27. try:
  28. self.parent_to_node(new_parent)
  29. except NodeError as e:
  30. raise e
  31. @property
  32. def root(self):
  33. if self._parent is None:
  34. return self
  35. return self._parent.root
  36. @property
  37. def name(self):
  38. return self._name
  39. @name.setter
  40. def name(self, value):
  41. if self._parent is not None:
  42. if self._parent.get_node(value) is not None:
  43. raise NodeError("Parent already contains node named '{}'.".format(name))
  44. self._name = value
  45. @property
  46. def full_name(self):
  47. if self._parent is None:
  48. return self._name
  49. return self._parent.full_name + "." + self._name
  50. @property
  51. def child_count(self):
  52. return len(this._children)
  53. def parent_to_node(self, parent, allow_reparenting=False):
  54. if not isinstance(value, Node):
  55. raise NodeError("Node may only parent to another Node instance.")
  56. if self._parent is None or self._parent != parent:
  57. if self._parent is not None:
  58. if allow_Reparenting == False:
  59. raise NodeError("Node already assigned a parent Node.")
  60. if self._parent.remove_node(self) != self:
  61. raise NodeError("Failed to remove self from current parent.")
  62. try:
  63. parent.attach_node(self)
  64. except NodeError as e:
  65. raise e
  66. def attach_node(self, node, reparent=False, index=-1):
  67. if node.parent is not None:
  68. if node.parent == self:
  69. return # Nothing to do. Given node already parented to this node.
  70. if reparent == False:
  71. raise NodeError("Node already parented.")
  72. if node.parent.remove_node(node) != node:
  73. raise NodeError("Failed to remove given node from it's current parent.")
  74. if self.get_node(node.name) is not None:
  75. raise NodeError("Node with name '{}' already attached.".format(node.name))
  76. node._parent = self
  77. if index < 0 or index >= len(self._children):
  78. self._children.append(node)
  79. else:
  80. self._children.insert(index, node)
  81. def remove_node(self, node):
  82. if isinstance(node, (str, unicode)):
  83. n = self.get_node(node)
  84. if n is not None:
  85. try:
  86. return self.remove_node(n)
  87. except NodeError as e:
  88. raise e
  89. elif isinstance(node, Node):
  90. if node.parent != self:
  91. if node.parent == None:
  92. raise NodeError("Cannot remove an unparented node.")
  93. try:
  94. return node.parent.remove_node(node)
  95. except NodeError as e:
  96. raise e
  97. if node in self._children:
  98. self._children.remove(node)
  99. node._parent = None
  100. return node
  101. else:
  102. raise NodeError("Expected a Node instance or a string.")
  103. return None
  104. def get_node(self, name):
  105. if len(self._children) <= 0:
  106. return None
  107. subnames = name.split(".")
  108. for c in self._children:
  109. if c.name == subnames[0]:
  110. if len(subnames) > 1:
  111. return c.get_node(".".join(subnames[1:-1]))
  112. return c
  113. return None
  114. def _update(self, dt):
  115. if hasattr(self, "on_update"):
  116. self.on_update(dt)
  117. for c in self._children:
  118. c._update(dt)
  119. def _render(self, surface):
  120. for c in self._children:
  121. c._render(surface)
  122. class Node2D(Node):
  123. def __init__(self, name="Node2D", parent=None):
  124. try:
  125. Node.__init__(self, name, parent)
  126. except NodeError as e:
  127. raise e
  128. def _render(self, surface):
  129. Node._render(self, surface)
  130. if hasattr(self, "on_render"):
  131. self._ACTIVE_SURF = surface
  132. self.on_render()
  133. del self._ACTIVE_SURF
  134. def blit(self, img, pos=(0,0), rect=None):
  135. if not hasattr(self, "_ACTIVE_SURF"):
  136. return
  137. self._ACTIVE_SURF.blit(img, pos, rect)
  138. def fill(self, color):
  139. if not hasattr(self, "_ACTIVE_SURF"):
  140. return
  141. self._ACTIVE_SURF.fill(color)
  142. def draw_lines(self, points, color, thickness=1, closed=False):
  143. if not hasattr(self, "_ACTIVE_SURF"):
  144. return
  145. pygame.draw.lines(self._ACTIVE_SURF, color, closed, points, thickness)
  146. def draw_rect(self, rect, color, thickness=1):
  147. if not hasattr(self, "_ACTIVE_SURF"):
  148. return
  149. pygame.draw.rect(self._ACTIVE_SURF, color, rect, thickness)
  150. def draw_ellipse(self, rect, color, thickness=1, fill_color=None):
  151. if not hasattr(self, "_ACTIVE_SURF"):
  152. return
  153. if fill_color is not None:
  154. pygame.draw.ellipse(self._ACTIVE_SURF, fill_color, rect)
  155. if thickness > 0:
  156. pygame.draw.ellipse(self._ACTIVE_SURF, color, rect, thickness)
  157. def draw_circle(self, pos, radius, color, thickness=1, fill_color=None):
  158. if not hasattr(self, "_ACTIVE_SURF"):
  159. return
  160. if fill_color is not None:
  161. pygame.draw.circle(self._ACTIVE_SURF, fill_color, pos, radius)
  162. if thickness > 0:
  163. pygame.draw.circle(self._ACTIVE_SURF, color, pos, radius, thickness)
  164. def draw_polygon(self, points, color, thickness=1, fill_color=None):
  165. if not hasattr(self, "_ACTIVE_SURF"):
  166. return
  167. if fill_color is not None:
  168. pygame.draw.polygon(self._ACTIVE_SURF, fill_color, points)
  169. if thickness >= 1:
  170. pygame.draw.polygon(self._ACTIVE_SURF, color, points, thickness)
  171. class NodeSurface(Node2D):
  172. def __init__(self, name="NodeSurface", parent=None):
  173. try:
  174. Node2D.__init__(self, name, parent)
  175. except NodeError as e:
  176. raise e
  177. self._offset = (0.0, 0.0)
  178. self._scale = (1.0, 1.0)
  179. self._scaleToDisplay = False
  180. self._scaleDirty = False
  181. self._keepAspectRatio = False
  182. self._alignCenter = False
  183. self._surface = None
  184. self._tsurface = None
  185. self.set_surface()
  186. def _updateTransformSurface(self):
  187. if self._surface is None:
  188. return
  189. self._scaleDirty = False
  190. if self._scaleToDisplay:
  191. dsize = Display.resolution
  192. ssize = self._surface.get_size()
  193. self._scale = (dsize[0] / ssize[0], dsize[1] / ssize[1])
  194. if self._keepAspectRatio:
  195. if self._scale[0] < self._scale[1]:
  196. self._scale = (self._scale[0], self._scale[0])
  197. else:
  198. self._scale = (self._scale[1], self._scale[1])
  199. if self._scale[0] == 1.0 and self._scale[1] == 1.0:
  200. self._tsurface = None
  201. return
  202. size = self._surface.get_size()
  203. nw = size[0] * self._scale[0]
  204. nh = 0
  205. if self._keepAspectRatio:
  206. nh = size[1] * self._scale[0]
  207. else:
  208. nh = size[1] * self._scale[1]
  209. self._tsurface = pygame.Surface((nw, nh), pygame.SRCALPHA, self._surface)
  210. self._tsurface.fill(pygame.Color(0,0,0,0))
  211. @property
  212. def resolution(self):
  213. if self._surface is None:
  214. return (0,0)
  215. return self._surface.get_size()
  216. @resolution.setter
  217. def resolution(self, res):
  218. try:
  219. self.set_surface(res)
  220. except (TypeError, ValueError) as e:
  221. raise e
  222. @property
  223. def width(self):
  224. return self.resolution[0]
  225. @property
  226. def height(self):
  227. return self.resolution[1]
  228. @property
  229. def offset(self):
  230. return self._offset
  231. @offset.setter
  232. def offset(self, offset):
  233. if not isinstance(offset, tuple):
  234. raise TypeError("Expected a tuple")
  235. if len(offset) != 2:
  236. raise ValueError("Expected tuple of length two.")
  237. if not isinstance(offset[0], (int, float)) or not isinstance(offset[1], (int, float)):
  238. raise TypeError("Expected number values.")
  239. self._offset = (float(offset[0]), float(offset[1]))
  240. @property
  241. def offset_x(self):
  242. return self._offset[0]
  243. @offset_x.setter
  244. def offset_x(self, x):
  245. if not isinstance(x, (int, float)):
  246. raise TypeError("Expected number value.")
  247. self._offset = (x, self._offset[1])
  248. @property
  249. def offset_y(self):
  250. return self._offset[1]
  251. @offset_y.setter
  252. def offset_y(self, y):
  253. if not isinstance(y, (int, float)):
  254. raise TypeError("Expected number value.")
  255. self._offset = (self._offset[0], y)
  256. @property
  257. def scale(self):
  258. return self._scale
  259. @scale.setter
  260. def scale(self, scale):
  261. if self._keepAspectRatio:
  262. if not isinstance(scale, (int, float)):
  263. raise TypeError("Expected number value.")
  264. self._scale = (scale, self._scale[1])
  265. else:
  266. if not isinstance(scale, tuple):
  267. raise TypeError("Expected a tuple")
  268. if len(scale) != 2:
  269. raise ValueError("Expected tuple of length two.")
  270. if not isinstance(scale[0], (int, float)) or not isinstance(scale[1], (int, float)):
  271. raise TypeError("Expected number values.")
  272. self._scale = scale
  273. self._updateTransformSurface()
  274. @property
  275. def keep_aspect_ratio(self):
  276. return self._keepAspectRatio
  277. @keep_aspect_ratio.setter
  278. def keep_aspect_ratio(self, keep):
  279. self._keepAspectRatio = (keep == True)
  280. self._updateTransformSurface()
  281. @property
  282. def align_center(self):
  283. return self._alignCenter
  284. @align_center.setter
  285. def align_center(self, center):
  286. self._alignCenter = (center == True)
  287. @property
  288. def scale_to_display(self):
  289. return self._scaleToDisplay
  290. @scale_to_display.setter
  291. def scale_to_display(self, todisplay):
  292. if todisplay == True:
  293. self._scaleToDisplay = True
  294. Events.listen("VIDEORESIZE", self._OnVideoResize)
  295. else:
  296. self._scaleToDisplay = False
  297. Events.unlisten("VIDEORESIZE", self._OnVideoResize)
  298. self._updateTransformSurface()
  299. def scale_to(self, target_resolution):
  300. if self._surface is not None:
  301. size = self._surface.get_size()
  302. nscale = (float(size[0]) / float(target_resolution[0]), float(size[1]) / float(target_resolution[1]))
  303. self.scale = nscale
  304. def set_surface(self, resolution=None):
  305. dsurf = Display.surface
  306. if resolution is None:
  307. if dsurf is not None:
  308. self._surface = dsurf.convert_alpha()
  309. self._surface.fill(pygame.Color(0,0,0,0))
  310. self._updateTransformSurface()
  311. else:
  312. if not isinstance(resolution, tuple):
  313. raise TypeError("Expected a tuple.")
  314. if len(resolution) != 2:
  315. raise ValueError("Expected a tuple of length two.")
  316. if not isinstance(resolution[0], int) or not isinstance(resolution[1], int):
  317. raise TypeError("Tuple expected to contain integers.")
  318. if dsurf is not None:
  319. self._surface = pygame.Surface(resolution, pygame.SRCALPHA, dsurf)
  320. else:
  321. self._surface = pygame.Surface(resolution, pygame.SRCALPHA)
  322. self._surface.fill(pygame.Color(0,0,0,0))
  323. self._updateTransformSurface()
  324. def _render(self, surface):
  325. if self._surface is None:
  326. self.set_surface()
  327. if self._surface is not None:
  328. if self._scaleDirty:
  329. self._updateTransformSurface()
  330. Node2D._render(self, self._surface)
  331. else:
  332. Node2D._render(self, surface)
  333. self._scale_and_blit(surface)
  334. def _scale_and_blit(self, dest):
  335. dsize = dest.get_size()
  336. src = self._surface
  337. if self._tsurface is not None:
  338. pygame.transform.scale(self._surface, self._tsurface.get_size(), self._tsurface)
  339. src = self._tsurface
  340. ssize = src.get_size()
  341. posx = self._offset[0]
  342. posy = self._offset[1]
  343. if self._alignCenter:
  344. if dsize[0] > ssize[0]:
  345. posx += (dsize[0] - ssize[0]) * 0.5
  346. if dsize[1] > ssize[1]:
  347. posy += (dsize[1] - ssize[1]) * 0.5
  348. pos = (int(posx), int(posy))
  349. dest.blit(src, pos)
  350. def _OnVideoResize(self, event, data):
  351. if self._scaleToDisplay:
  352. self._scaleDirty = True