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.

541 lines
17KB

  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. from .resource import Manager
  10. import pygame
  11. class NodeError(Exception):
  12. pass
  13. class Node:
  14. def __init__(self, name="Node", parent=None):
  15. self._NODE_DATA={
  16. "parent":None,
  17. "name":name,
  18. "children":[]
  19. "resource":None
  20. }
  21. if parent is not None:
  22. try:
  23. self.parent = parent
  24. except NodeError as e:
  25. raise e
  26. @property
  27. def parent(self):
  28. return self._NODE_DATA["parent"]
  29. @parent.setter
  30. def parent(self, new_parent):
  31. try:
  32. self.parent_to_node(new_parent)
  33. except NodeError as e:
  34. raise e
  35. @property
  36. def root(self):
  37. if self.parent is None:
  38. return self
  39. return self.parent.root
  40. @property
  41. def name(self):
  42. return self._NODE_DATA["name"]
  43. @name.setter
  44. def name(self, value):
  45. if self.parent is not None:
  46. if self.parent.get_node(value) is not None:
  47. raise NodeError("Parent already contains node named '{}'.".format(name))
  48. self._NODE_DATA["name"] = value
  49. @property
  50. def full_name(self):
  51. if self.parent is None:
  52. return self.name
  53. return self.parent.full_name + "." + self.name
  54. @property
  55. def child_count(self):
  56. return len(this._NODE_DATA["children"])
  57. def parent_to_node(self, parent, allow_reparenting=False):
  58. if not isinstance(value, Node):
  59. raise NodeError("Node may only parent to another Node instance.")
  60. if self.parent is None or self.parent != parent:
  61. if self.parent is not None:
  62. if allow_Reparenting == False:
  63. raise NodeError("Node already assigned a parent Node.")
  64. if self.parent.remove_node(self) != self:
  65. raise NodeError("Failed to remove self from current parent.")
  66. try:
  67. parent.attach_node(self)
  68. except NodeError as e:
  69. raise e
  70. def attach_node(self, node, reparent=False, index=-1):
  71. if node.parent is not None:
  72. if node.parent == self:
  73. return # Nothing to do. Given node already parented to this node.
  74. if reparent == False:
  75. raise NodeError("Node already parented.")
  76. if node.parent.remove_node(node) != node:
  77. raise NodeError("Failed to remove given node from it's current parent.")
  78. if self.get_node(node.name) is not None:
  79. raise NodeError("Node with name '{}' already attached.".format(node.name))
  80. node._NODE_DATA["parent"] = self
  81. children = self._NODE_DATA["children"]
  82. if index < 0 or index >= len(children):
  83. children.append(node)
  84. else:
  85. children.insert(index, node)
  86. def remove_node(self, node):
  87. if isinstance(node, (str, unicode)):
  88. n = self.get_node(node)
  89. if n is not None:
  90. try:
  91. return self.remove_node(n)
  92. except NodeError as e:
  93. raise e
  94. elif isinstance(node, Node):
  95. if node.parent != self:
  96. if node.parent == None:
  97. raise NodeError("Cannot remove an unparented node.")
  98. try:
  99. return node.parent.remove_node(node)
  100. except NodeError as e:
  101. raise e
  102. if node in self._NODE_DATA["children"]:
  103. self._NODE_DATA["children"].remove(node)
  104. node._NODE_DATA["parent"] = None
  105. return node
  106. else:
  107. raise NodeError("Expected a Node instance or a string.")
  108. return None
  109. def get_node(self, name):
  110. if self.child_count <= 0:
  111. return None
  112. subnames = name.split(".")
  113. for c in self._NODE_DATA["children"]:
  114. if c.name == subnames[0]:
  115. if len(subnames) > 1:
  116. return c.get_node(".".join(subnames[1:-1]))
  117. return c
  118. return None
  119. def _update(self, dt):
  120. if hasattr(self, "on_update"):
  121. self.on_update(dt)
  122. for c in self._NODE_DATA["children"]:
  123. c._update(dt)
  124. def _render(self, surface):
  125. for c in self._NODE_DATA["children"]:
  126. c._render(surface)
  127. class Node2D(Node):
  128. def __init__(self, name="Node2D", parent=None):
  129. try:
  130. Node.__init__(self, name, parent)
  131. except NodeError as e:
  132. raise e
  133. def _render(self, surface):
  134. Node._render(self, surface)
  135. if hasattr(self, "on_render"):
  136. self._ACTIVE_SURF = surface
  137. self.on_render()
  138. del self._ACTIVE_SURF
  139. def draw_image(self, img, pos=(0,0), rect=None):
  140. if not hasattr(self, "_ACTIVE_SURF"):
  141. return
  142. self._ACTIVE_SURF.blit(img, pos, rect)
  143. def fill(self, color):
  144. if not hasattr(self, "_ACTIVE_SURF"):
  145. return
  146. self._ACTIVE_SURF.fill(color)
  147. def draw_lines(self, points, color, thickness=1, closed=False):
  148. if not hasattr(self, "_ACTIVE_SURF"):
  149. return
  150. pygame.draw.lines(self._ACTIVE_SURF, color, closed, points, thickness)
  151. def draw_rect(self, rect, color, thickness=1):
  152. if not hasattr(self, "_ACTIVE_SURF"):
  153. return
  154. pygame.draw.rect(self._ACTIVE_SURF, color, rect, thickness)
  155. def draw_ellipse(self, rect, color, thickness=1, fill_color=None):
  156. if not hasattr(self, "_ACTIVE_SURF"):
  157. return
  158. if fill_color is not None:
  159. pygame.draw.ellipse(self._ACTIVE_SURF, fill_color, rect)
  160. if thickness > 0:
  161. pygame.draw.ellipse(self._ACTIVE_SURF, color, rect, thickness)
  162. def draw_circle(self, pos, radius, color, thickness=1, fill_color=None):
  163. if not hasattr(self, "_ACTIVE_SURF"):
  164. return
  165. if fill_color is not None:
  166. pygame.draw.circle(self._ACTIVE_SURF, fill_color, pos, radius)
  167. if thickness > 0:
  168. pygame.draw.circle(self._ACTIVE_SURF, color, pos, radius, thickness)
  169. def draw_polygon(self, points, color, thickness=1, fill_color=None):
  170. if not hasattr(self, "_ACTIVE_SURF"):
  171. return
  172. if fill_color is not None:
  173. pygame.draw.polygon(self._ACTIVE_SURF, fill_color, points)
  174. if thickness >= 1:
  175. pygame.draw.polygon(self._ACTIVE_SURF, color, points, thickness)
  176. class NodeSurface(Node2D):
  177. def __init__(self, name="NodeSurface", parent=None):
  178. try:
  179. Node2D.__init__(self, name, parent)
  180. except NodeError as e:
  181. raise e
  182. # TODO: Update this class to use the _NODE*_DATA={} structure.
  183. self._offset = (0.0, 0.0)
  184. self._scale = (1.0, 1.0)
  185. self._scaleToDisplay = False
  186. self._scaleDirty = False
  187. self._keepAspectRatio = False
  188. self._alignCenter = False
  189. self._surface = None
  190. self._tsurface = None
  191. self.set_surface()
  192. def _updateTransformSurface(self):
  193. if self._surface is None:
  194. return
  195. self._scaleDirty = False
  196. if self._scaleToDisplay:
  197. dsize = Display.resolution
  198. ssize = self._surface.get_size()
  199. self._scale = (dsize[0] / ssize[0], dsize[1] / ssize[1])
  200. if self._keepAspectRatio:
  201. if self._scale[0] < self._scale[1]:
  202. self._scale = (self._scale[0], self._scale[0])
  203. else:
  204. self._scale = (self._scale[1], self._scale[1])
  205. if self._scale[0] == 1.0 and self._scale[1] == 1.0:
  206. self._tsurface = None
  207. return
  208. size = self._surface.get_size()
  209. nw = size[0] * self._scale[0]
  210. nh = 0
  211. if self._keepAspectRatio:
  212. nh = size[1] * self._scale[0]
  213. else:
  214. nh = size[1] * self._scale[1]
  215. self._tsurface = pygame.Surface((nw, nh), pygame.SRCALPHA, self._surface)
  216. self._tsurface.fill(pygame.Color(0,0,0,0))
  217. @property
  218. def resolution(self):
  219. if self._surface is None:
  220. return (0,0)
  221. return self._surface.get_size()
  222. @resolution.setter
  223. def resolution(self, res):
  224. try:
  225. self.set_surface(res)
  226. except (TypeError, ValueError) as e:
  227. raise e
  228. @property
  229. def width(self):
  230. return self.resolution[0]
  231. @property
  232. def height(self):
  233. return self.resolution[1]
  234. @property
  235. def offset(self):
  236. return self._offset
  237. @offset.setter
  238. def offset(self, offset):
  239. if not isinstance(offset, tuple):
  240. raise TypeError("Expected a tuple")
  241. if len(offset) != 2:
  242. raise ValueError("Expected tuple of length two.")
  243. if not isinstance(offset[0], (int, float)) or not isinstance(offset[1], (int, float)):
  244. raise TypeError("Expected number values.")
  245. self._offset = (float(offset[0]), float(offset[1]))
  246. @property
  247. def offset_x(self):
  248. return self._offset[0]
  249. @offset_x.setter
  250. def offset_x(self, x):
  251. if not isinstance(x, (int, float)):
  252. raise TypeError("Expected number value.")
  253. self._offset = (x, self._offset[1])
  254. @property
  255. def offset_y(self):
  256. return self._offset[1]
  257. @offset_y.setter
  258. def offset_y(self, y):
  259. if not isinstance(y, (int, float)):
  260. raise TypeError("Expected number value.")
  261. self._offset = (self._offset[0], y)
  262. @property
  263. def scale(self):
  264. return self._scale
  265. @scale.setter
  266. def scale(self, scale):
  267. if self._keepAspectRatio:
  268. if not isinstance(scale, (int, float)):
  269. raise TypeError("Expected number value.")
  270. self._scale = (scale, self._scale[1])
  271. else:
  272. if not isinstance(scale, tuple):
  273. raise TypeError("Expected a tuple")
  274. if len(scale) != 2:
  275. raise ValueError("Expected tuple of length two.")
  276. if not isinstance(scale[0], (int, float)) or not isinstance(scale[1], (int, float)):
  277. raise TypeError("Expected number values.")
  278. self._scale = scale
  279. self._updateTransformSurface()
  280. @property
  281. def keep_aspect_ratio(self):
  282. return self._keepAspectRatio
  283. @keep_aspect_ratio.setter
  284. def keep_aspect_ratio(self, keep):
  285. self._keepAspectRatio = (keep == True)
  286. self._updateTransformSurface()
  287. @property
  288. def align_center(self):
  289. return self._alignCenter
  290. @align_center.setter
  291. def align_center(self, center):
  292. self._alignCenter = (center == True)
  293. @property
  294. def scale_to_display(self):
  295. return self._scaleToDisplay
  296. @scale_to_display.setter
  297. def scale_to_display(self, todisplay):
  298. if todisplay == True:
  299. self._scaleToDisplay = True
  300. Events.listen("VIDEORESIZE", self._OnVideoResize)
  301. else:
  302. self._scaleToDisplay = False
  303. Events.unlisten("VIDEORESIZE", self._OnVideoResize)
  304. self._updateTransformSurface()
  305. def scale_to(self, target_resolution):
  306. if self._surface is not None:
  307. size = self._surface.get_size()
  308. nscale = (float(size[0]) / float(target_resolution[0]), float(size[1]) / float(target_resolution[1]))
  309. self.scale = nscale
  310. def set_surface(self, resolution=None):
  311. dsurf = Display.surface
  312. if resolution is None:
  313. if dsurf is not None:
  314. self._surface = dsurf.convert_alpha()
  315. self._surface.fill(pygame.Color(0,0,0,0))
  316. self._updateTransformSurface()
  317. else:
  318. if not isinstance(resolution, tuple):
  319. raise TypeError("Expected a tuple.")
  320. if len(resolution) != 2:
  321. raise ValueError("Expected a tuple of length two.")
  322. if not isinstance(resolution[0], int) or not isinstance(resolution[1], int):
  323. raise TypeError("Tuple expected to contain integers.")
  324. if dsurf is not None:
  325. self._surface = pygame.Surface(resolution, pygame.SRCALPHA, dsurf)
  326. else:
  327. self._surface = pygame.Surface(resolution, pygame.SRCALPHA)
  328. self._surface.fill(pygame.Color(0,0,0,0))
  329. self._updateTransformSurface()
  330. def _render(self, surface):
  331. if self._surface is None:
  332. self.set_surface()
  333. if self._surface is not None:
  334. if self._scaleDirty:
  335. self._updateTransformSurface()
  336. Node2D._render(self, self._surface)
  337. else:
  338. Node2D._render(self, surface)
  339. self._scale_and_blit(surface)
  340. def _scale_and_blit(self, dest):
  341. dsize = dest.get_size()
  342. src = self._surface
  343. if self._tsurface is not None:
  344. pygame.transform.scale(self._surface, self._tsurface.get_size(), self._tsurface)
  345. src = self._tsurface
  346. ssize = src.get_size()
  347. posx = self._offset[0]
  348. posy = self._offset[1]
  349. if self._alignCenter:
  350. if dsize[0] > ssize[0]:
  351. posx += (dsize[0] - ssize[0]) * 0.5
  352. if dsize[1] > ssize[1]:
  353. posy += (dsize[1] - ssize[1]) * 0.5
  354. pos = (int(posx), int(posy))
  355. dest.blit(src, pos)
  356. def _OnVideoResize(self, event, data):
  357. if self._scaleToDisplay:
  358. self._scaleDirty = True
  359. class NodeSprite(Node2D):
  360. def __init__(self, name="NodeSprite", parent=None):
  361. try:
  362. Node2D.__init__(self, name, parent)
  363. except NodeError as e:
  364. raise e
  365. self._NODESPRITE_DATA={
  366. "rect":[0,0,0,0],
  367. "image":"",
  368. "scale":[1.0, 1.0],
  369. "surface":None
  370. }
  371. @property
  372. def rect(self):
  373. return (self._NODESPRITE_DATA["rect"][0],
  374. self._NODESPRITE_DATA["rect"][1],
  375. self._NODESPRITE_DATA["rect"][2],
  376. self._NODESPRITE_DATA["rect"][3])
  377. @rect.setter
  378. def rect(self, rect):
  379. if not isinstance(rect, (list, tuple)):
  380. raise TypeError("Expected a list or tuple.")
  381. if len(rect) != 4:
  382. raise ValueError("rect value contains wrong number of values.")
  383. try:
  384. self.rect_x = rect[0]
  385. self.rect_y = rect[1]
  386. self.rect_width = rect[2]
  387. self.rect_height = rect[3]
  388. except Exception as e:
  389. raise e
  390. @property
  391. def rect_x(self):
  392. return self._NODESPRITE_DATA["rect"][0]
  393. @rect_x.setter
  394. def rect_x(self, v):
  395. if not isinstance(v, int):
  396. raise TypeError("Expected integer value.")
  397. self._NODESPRITE_DATA["rect"][0] = v
  398. @property
  399. def rect_y(self):
  400. return self._NODESPRITE_DATA["rect"][1]
  401. @rect_y.setter
  402. def rect_y(self, v):
  403. if not isinstance(v, int):
  404. raise TypeError("Expected integer value.")
  405. self._NODESPRITE_DATA["rect"][1] = v
  406. @property
  407. def rect_width(self):
  408. return self._NODESPRITE_DATA["rect"][2]
  409. @rect_width.setter
  410. def rect_width(self, v):
  411. if not isinstance(v, int):
  412. raise TypeError("Expected integer value.")
  413. self._NODESPRITE_DATA["rect"][2] = v
  414. @property
  415. def rect_height(self):
  416. return self._NODESPRITE_DATA["rect"][3]
  417. @rect_height.setter
  418. def rect_height(self, v):
  419. if not isinstance(v, int):
  420. raise TypeError("Expected integer value.")
  421. self._NODESPRITE_DATA["rect"][3] = v
  422. @property
  423. def center(self):
  424. r = self._NODESPRITE_DATA["rect"]
  425. return (int(r[0] + (r[2] * 0.5)), int(r[1] + (r[3] * 0.5)))
  426. @property
  427. def scale(self):
  428. return (self._NODESPRITE_DATA["scale"][0], self._NODESPRITE_DATA["scale"][1])
  429. @scale.setter(self, scale):
  430. if not isinstance(scale, (list, tuple)):
  431. raise TypeError("Expected a list or tuple.")
  432. if len(scale) != 2:
  433. raise ValueError("Scale contains wrong number of values.")
  434. try:
  435. self.scale_x = scale[0]
  436. self.scale_y = scale[1]
  437. except Exception as e:
  438. raise e
  439. @property
  440. def scale_x(self):
  441. return self._NODESPRITE_DATA["scale"][0]
  442. @scale_x.setter
  443. def scale_x(self, v):
  444. if not isinstance(v, (int, float)):
  445. raise TypeError("Expected number value.")
  446. self._NODESPRITE_DATA["scale"][0] = float(v)
  447. @property
  448. def scale_y(self):
  449. return self._NODESPRITE_DATA["scale"][1]
  450. @scale_y.setter
  451. def scale_y(self, v):
  452. if not isinstance(v, (int, float)):
  453. raise TypeError("Expected number value.")
  454. self._NODESPRITE_DATA["scale"][1] = float(v)
  455. @property
  456. def image(self):
  457. return self._NODESPRITE_DATA["image"]
  458. @image.setter
  459. def image(self, image):
  460. if self._NODESPRITE_DATA["image"] != "":
  461. self._NODESPRITE_DATA["surface"] = None # Clear reference to original surface.
  462. pass