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.

700 lines
23KB

  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 ResourceManager
  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 resource(self):
  56. if self._NODE_DATA["resource"] is None:
  57. # Only bother creating the instance if it's being asked for.
  58. # All ResourceManager instances access same data.
  59. self._NODE_DATA["resource"] = ResourceManager()
  60. return self._NODE_DATA["resource"]
  61. @property
  62. def child_count(self):
  63. return len(this._NODE_DATA["children"])
  64. def parent_to_node(self, parent, allow_reparenting=False):
  65. if not isinstance(value, Node):
  66. raise NodeError("Node may only parent to another Node instance.")
  67. if self.parent is None or self.parent != parent:
  68. if self.parent is not None:
  69. if allow_Reparenting == False:
  70. raise NodeError("Node already assigned a parent Node.")
  71. if self.parent.remove_node(self) != self:
  72. raise NodeError("Failed to remove self from current parent.")
  73. try:
  74. parent.attach_node(self)
  75. except NodeError as e:
  76. raise e
  77. def attach_node(self, node, reparent=False, index=-1):
  78. if node.parent is not None:
  79. if node.parent == self:
  80. return # Nothing to do. Given node already parented to this node.
  81. if reparent == False:
  82. raise NodeError("Node already parented.")
  83. if node.parent.remove_node(node) != node:
  84. raise NodeError("Failed to remove given node from it's current parent.")
  85. if self.get_node(node.name) is not None:
  86. raise NodeError("Node with name '{}' already attached.".format(node.name))
  87. node._NODE_DATA["parent"] = self
  88. children = self._NODE_DATA["children"]
  89. if index < 0 or index >= len(children):
  90. children.append(node)
  91. else:
  92. children.insert(index, node)
  93. def remove_node(self, node):
  94. if isinstance(node, (str, unicode)):
  95. n = self.get_node(node)
  96. if n is not None:
  97. try:
  98. return self.remove_node(n)
  99. except NodeError as e:
  100. raise e
  101. elif isinstance(node, Node):
  102. if node.parent != self:
  103. if node.parent == None:
  104. raise NodeError("Cannot remove an unparented node.")
  105. try:
  106. return node.parent.remove_node(node)
  107. except NodeError as e:
  108. raise e
  109. if node in self._NODE_DATA["children"]:
  110. self._NODE_DATA["children"].remove(node)
  111. node._NODE_DATA["parent"] = None
  112. return node
  113. else:
  114. raise NodeError("Expected a Node instance or a string.")
  115. return None
  116. def get_node(self, name):
  117. if self.child_count <= 0:
  118. return None
  119. subnames = name.split(".")
  120. for c in self._NODE_DATA["children"]:
  121. if c.name == subnames[0]:
  122. if len(subnames) > 1:
  123. return c.get_node(".".join(subnames[1:-1]))
  124. return c
  125. return None
  126. def _update(self, dt):
  127. if hasattr(self, "on_update"):
  128. self.on_update(dt)
  129. for c in self._NODE_DATA["children"]:
  130. c._update(dt)
  131. def _render(self, surface):
  132. for c in self._NODE_DATA["children"]:
  133. c._render(surface)
  134. class Node2D(Node):
  135. def __init__(self, name="Node2D", parent=None):
  136. try:
  137. Node.__init__(self, name, parent)
  138. except NodeError as e:
  139. raise e
  140. self._NODE2D_DATA={
  141. "position":[0.0, 0.0]
  142. }
  143. @property
  144. def position(self):
  145. p = self._NODE2D_DATA["position"]
  146. return (p[0], p[1])
  147. @position.setter
  148. def position(self, pos):
  149. if not isinstance(pos, (list, tuple)):
  150. raise TypeError("Expected a list or tuple.")
  151. if len(pos) != 2:
  152. raise ValueError("Wrong number of values given.")
  153. try:
  154. self.position_x = pos[0]
  155. self.position_y = pos[1]
  156. except Exception as e:
  157. raise e
  158. @property
  159. def position_x(self):
  160. return self._NODE2D_DATA["position"][0]
  161. @position_x.setter
  162. def position_x(self, v):
  163. if not isinstance(v, (int, float)):
  164. raise TypeError("Excepted an number value.")
  165. self._NODE2D_DATA["position"][0] = float(v)
  166. @property
  167. def position_y(self):
  168. return self._NODE2D_DATA["position"][1]
  169. @position_y.setter
  170. def position_y(self, v):
  171. if not isinstance(v, (int, float)):
  172. raise TypeError("Excepted an number value.")
  173. self._NODE2D_DATA["position"][1] = float(v)
  174. def _callOnRender(self, surface):
  175. if hasattr(self, "on_render"):
  176. self._ACTIVE_SURF = surface
  177. self.on_render()
  178. del self._ACTIVE_SURF
  179. def _render(self, surface):
  180. self._callOnRender(surface)
  181. Node._render(self, surface)
  182. def draw_image(self, img, pos=(0,0), rect=None):
  183. if not hasattr(self, "_ACTIVE_SURF"):
  184. return
  185. self._ACTIVE_SURF.blit(img, pos, rect)
  186. def fill(self, color):
  187. if not hasattr(self, "_ACTIVE_SURF"):
  188. return
  189. self._ACTIVE_SURF.fill(color)
  190. def draw_lines(self, points, color, thickness=1, closed=False):
  191. if not hasattr(self, "_ACTIVE_SURF"):
  192. return
  193. pygame.draw.lines(self._ACTIVE_SURF, color, closed, points, thickness)
  194. def draw_rect(self, rect, color, thickness=1):
  195. if not hasattr(self, "_ACTIVE_SURF"):
  196. return
  197. pygame.draw.rect(self._ACTIVE_SURF, color, rect, thickness)
  198. def draw_ellipse(self, rect, color, thickness=1, fill_color=None):
  199. if not hasattr(self, "_ACTIVE_SURF"):
  200. return
  201. if fill_color is not None:
  202. pygame.draw.ellipse(self._ACTIVE_SURF, fill_color, rect)
  203. if thickness > 0:
  204. pygame.draw.ellipse(self._ACTIVE_SURF, color, rect, thickness)
  205. def draw_circle(self, pos, radius, color, thickness=1, fill_color=None):
  206. if not hasattr(self, "_ACTIVE_SURF"):
  207. return
  208. if fill_color is not None:
  209. pygame.draw.circle(self._ACTIVE_SURF, fill_color, pos, radius)
  210. if thickness > 0:
  211. pygame.draw.circle(self._ACTIVE_SURF, color, pos, radius, thickness)
  212. def draw_polygon(self, points, color, thickness=1, fill_color=None):
  213. if not hasattr(self, "_ACTIVE_SURF"):
  214. return
  215. if fill_color is not None:
  216. pygame.draw.polygon(self._ACTIVE_SURF, fill_color, points)
  217. if thickness >= 1:
  218. pygame.draw.polygon(self._ACTIVE_SURF, color, points, thickness)
  219. class NodeSurface(Node2D):
  220. def __init__(self, name="NodeSurface", parent=None):
  221. try:
  222. Node2D.__init__(self, name, parent)
  223. except NodeError as e:
  224. raise e
  225. # TODO: Update this class to use the _NODE*_DATA={} structure.
  226. self._scale = (1.0, 1.0)
  227. self._scaleToDisplay = False
  228. self._scaleDirty = False
  229. self._keepAspectRatio = False
  230. self._alignCenter = False
  231. self._surface = None
  232. self._tsurface = None
  233. self.set_surface()
  234. def _updateTransformSurface(self):
  235. if self._surface is None:
  236. return
  237. self._scaleDirty = False
  238. if self._scaleToDisplay:
  239. dsize = Display.resolution
  240. ssize = self._surface.get_size()
  241. self._scale = (dsize[0] / ssize[0], dsize[1] / ssize[1])
  242. if self._keepAspectRatio:
  243. if self._scale[0] < self._scale[1]:
  244. self._scale = (self._scale[0], self._scale[0])
  245. else:
  246. self._scale = (self._scale[1], self._scale[1])
  247. if self._scale[0] == 1.0 and self._scale[1] == 1.0:
  248. self._tsurface = None
  249. return
  250. size = self._surface.get_size()
  251. nw = size[0] * self._scale[0]
  252. nh = 0
  253. if self._keepAspectRatio:
  254. nh = size[1] * self._scale[0]
  255. else:
  256. nh = size[1] * self._scale[1]
  257. self._tsurface = pygame.Surface((nw, nh), pygame.SRCALPHA, self._surface)
  258. self._tsurface.fill(pygame.Color(0,0,0,0))
  259. @property
  260. def resolution(self):
  261. if self._surface is None:
  262. return (0,0)
  263. return self._surface.get_size()
  264. @resolution.setter
  265. def resolution(self, res):
  266. try:
  267. self.set_surface(res)
  268. except (TypeError, ValueError) as e:
  269. raise e
  270. @property
  271. def width(self):
  272. return self.resolution[0]
  273. @property
  274. def height(self):
  275. return self.resolution[1]
  276. @property
  277. def scale(self):
  278. return self._scale
  279. @scale.setter
  280. def scale(self, scale):
  281. if self._keepAspectRatio:
  282. if not isinstance(scale, (int, float)):
  283. raise TypeError("Expected number value.")
  284. self._scale = (scale, self._scale[1])
  285. else:
  286. if not isinstance(scale, tuple):
  287. raise TypeError("Expected a tuple")
  288. if len(scale) != 2:
  289. raise ValueError("Expected tuple of length two.")
  290. if not isinstance(scale[0], (int, float)) or not isinstance(scale[1], (int, float)):
  291. raise TypeError("Expected number values.")
  292. self._scale = scale
  293. self._updateTransformSurface()
  294. @property
  295. def keep_aspect_ratio(self):
  296. return self._keepAspectRatio
  297. @keep_aspect_ratio.setter
  298. def keep_aspect_ratio(self, keep):
  299. self._keepAspectRatio = (keep == True)
  300. self._updateTransformSurface()
  301. @property
  302. def align_center(self):
  303. return self._alignCenter
  304. @align_center.setter
  305. def align_center(self, center):
  306. self._alignCenter = (center == True)
  307. @property
  308. def scale_to_display(self):
  309. return self._scaleToDisplay
  310. @scale_to_display.setter
  311. def scale_to_display(self, todisplay):
  312. if todisplay == True:
  313. self._scaleToDisplay = True
  314. Events.listen("VIDEORESIZE", self._OnVideoResize)
  315. else:
  316. self._scaleToDisplay = False
  317. Events.unlisten("VIDEORESIZE", self._OnVideoResize)
  318. self._updateTransformSurface()
  319. def scale_to(self, target_resolution):
  320. if self._surface is not None:
  321. size = self._surface.get_size()
  322. nscale = (float(size[0]) / float(target_resolution[0]), float(size[1]) / float(target_resolution[1]))
  323. self.scale = nscale
  324. def set_surface(self, resolution=None):
  325. dsurf = Display.surface
  326. if resolution is None:
  327. if dsurf is not None:
  328. self._surface = dsurf.convert_alpha()
  329. self._surface.fill(pygame.Color(0,0,0,0))
  330. self._updateTransformSurface()
  331. else:
  332. if not isinstance(resolution, tuple):
  333. raise TypeError("Expected a tuple.")
  334. if len(resolution) != 2:
  335. raise ValueError("Expected a tuple of length two.")
  336. if not isinstance(resolution[0], int) or not isinstance(resolution[1], int):
  337. raise TypeError("Tuple expected to contain integers.")
  338. if dsurf is not None:
  339. self._surface = pygame.Surface(resolution, pygame.SRCALPHA, dsurf)
  340. else:
  341. self._surface = pygame.Surface(resolution, pygame.SRCALPHA)
  342. self._surface.fill(pygame.Color(0,0,0,0))
  343. self._updateTransformSurface()
  344. def _render(self, surface):
  345. if self._surface is None:
  346. self.set_surface()
  347. if self._surface is not None:
  348. if self._scaleDirty:
  349. self._updateTransformSurface()
  350. Node2D._render(self, self._surface)
  351. else:
  352. Node2D._render(self, surface)
  353. self._scale_and_blit(surface)
  354. def _scale_and_blit(self, dest):
  355. dsize = dest.get_size()
  356. src = self._surface
  357. if self._tsurface is not None:
  358. pygame.transform.scale(self._surface, self._tsurface.get_size(), self._tsurface)
  359. src = self._tsurface
  360. ssize = src.get_size()
  361. posx = self.position_x
  362. posy = self.position_y
  363. if self._alignCenter:
  364. if dsize[0] > ssize[0]:
  365. posx += (dsize[0] - ssize[0]) * 0.5
  366. if dsize[1] > ssize[1]:
  367. posy += (dsize[1] - ssize[1]) * 0.5
  368. pos = (int(posx), int(posy))
  369. dest.blit(src, pos)
  370. def _OnVideoResize(self, event, data):
  371. if self._scaleToDisplay:
  372. self._scaleDirty = True
  373. class NodeSprite(Node2D):
  374. def __init__(self, name="NodeSprite", parent=None):
  375. try:
  376. Node2D.__init__(self, name, parent)
  377. except NodeError as e:
  378. raise e
  379. self._NODESPRITE_DATA={
  380. "rect":[0,0,0,0],
  381. "image":"",
  382. "scale":[1.0, 1.0],
  383. "surface":None
  384. }
  385. @property
  386. def image_width(self):
  387. if self._NODESPRITE_DATA["surface"] is None:
  388. return 0
  389. surf = self._NODESPRITE_DATA["surface"]()
  390. return surf.get_width()
  391. @property
  392. def image_height(self):
  393. if self._NODESPRITE_DATA["surface"] is None:
  394. return 0
  395. surf = self._NODESPRITE_DATA["surface"]()
  396. return surf.get_height()
  397. @property
  398. def sprite_width(self):
  399. return int(self.rect_width * self.scale_x)
  400. @property
  401. def sprite_height(self):
  402. return int(self.rect_height * self.scale_y)
  403. @property
  404. def rect(self):
  405. return (self._NODESPRITE_DATA["rect"][0],
  406. self._NODESPRITE_DATA["rect"][1],
  407. self._NODESPRITE_DATA["rect"][2],
  408. self._NODESPRITE_DATA["rect"][3])
  409. @rect.setter
  410. def rect(self, rect):
  411. if not isinstance(rect, (list, tuple)):
  412. raise TypeError("Expected a list or tuple.")
  413. if len(rect) != 4:
  414. raise ValueError("rect value contains wrong number of values.")
  415. try:
  416. self.rect_x = rect[0]
  417. self.rect_y = rect[1]
  418. self.rect_width = rect[2]
  419. self.rect_height = rect[3]
  420. except Exception as e:
  421. raise e
  422. @property
  423. def rect_x(self):
  424. return self._NODESPRITE_DATA["rect"][0]
  425. @rect_x.setter
  426. def rect_x(self, v):
  427. if not isinstance(v, int):
  428. raise TypeError("Expected integer value.")
  429. self._NODESPRITE_DATA["rect"][0] = v
  430. self._NODESPRITE_ValidateRect()
  431. @property
  432. def rect_y(self):
  433. return self._NODESPRITE_DATA["rect"][1]
  434. @rect_y.setter
  435. def rect_y(self, v):
  436. if not isinstance(v, int):
  437. raise TypeError("Expected integer value.")
  438. self._NODESPRITE_DATA["rect"][1] = v
  439. self._NODESPRITE_ValidateRect()
  440. @property
  441. def rect_width(self):
  442. return self._NODESPRITE_DATA["rect"][2]
  443. @rect_width.setter
  444. def rect_width(self, v):
  445. if not isinstance(v, int):
  446. raise TypeError("Expected integer value.")
  447. self._NODESPRITE_DATA["rect"][2] = v
  448. self._NODESPRITE_ValidateRect()
  449. @property
  450. def rect_height(self):
  451. return self._NODESPRITE_DATA["rect"][3]
  452. @rect_height.setter
  453. def rect_height(self, v):
  454. if not isinstance(v, int):
  455. raise TypeError("Expected integer value.")
  456. self._NODESPRITE_DATA["rect"][3] = v
  457. self._NODESPRITE_ValidateRect()
  458. @property
  459. def center(self):
  460. r = self._NODESPRITE_DATA["rect"]
  461. return (int(r[0] + (r[2] * 0.5)), int(r[1] + (r[3] * 0.5)))
  462. @property
  463. def scale(self):
  464. return (self._NODESPRITE_DATA["scale"][0], self._NODESPRITE_DATA["scale"][1])
  465. @scale.setter
  466. def scale(self, scale):
  467. if not isinstance(scale, (list, tuple)):
  468. raise TypeError("Expected a list or tuple.")
  469. if len(scale) != 2:
  470. raise ValueError("Scale contains wrong number of values.")
  471. try:
  472. self.scale_x = scale[0]
  473. self.scale_y = scale[1]
  474. except Exception as e:
  475. raise e
  476. @property
  477. def scale_x(self):
  478. return self._NODESPRITE_DATA["scale"][0]
  479. @scale_x.setter
  480. def scale_x(self, v):
  481. if not isinstance(v, (int, float)):
  482. raise TypeError("Expected number value.")
  483. self._NODESPRITE_DATA["scale"][0] = float(v)
  484. self._NODESPRITE_DATA["scale_dirty"] = True
  485. @property
  486. def scale_y(self):
  487. return self._NODESPRITE_DATA["scale"][1]
  488. @scale_y.setter
  489. def scale_y(self, v):
  490. if not isinstance(v, (int, float)):
  491. raise TypeError("Expected number value.")
  492. self._NODESPRITE_DATA["scale"][1] = float(v)
  493. self._NODESPRITE_DATA["scale_dirty"] = True
  494. @property
  495. def image(self):
  496. return self._NODESPRITE_DATA["image"]
  497. @image.setter
  498. def image(self, src):
  499. src = src.strip()
  500. if self._NODESPRITE_DATA["image"] == src:
  501. return # Nothing to change... lol
  502. if self._NODESPRITE_DATA["image"] != "":
  503. self._NODESPRITE_DATA["surface"] = None # Clear reference to original surface.
  504. if src != "":
  505. rm = self.resource
  506. try:
  507. if not rm.has("graphic", src):
  508. rm.store("graphic", src)
  509. self._NODESPRITE_DATA["image"] = src
  510. self._NODESPRITE_DATA["surface"] = rm.get("graphic", src)
  511. if self._NODESPRITE_DATA["surface"] is None:
  512. self._NODESPRITE_DATA["image"] = ""
  513. self._NODESPRITE_DATA["rect"]=[0,0,0,0]
  514. else:
  515. # Resetting the rect to identity for the new image.
  516. surf = self._NODESPRITE_DATA["surface"]()
  517. size = surf.get_size()
  518. self._NODESPRITE_DATA["rect"]=[0,0,size[0], size[1]]
  519. except Exception as e:
  520. raise e
  521. else:
  522. self._NODESPRITE_DATA["image"] = ""
  523. self._NODESPRITE_DATA["rect"]=[0,0,0,0]
  524. def _render(self, surface):
  525. # Call the on_render() method, if any
  526. Node2D._callOnRender(self, surface)
  527. # Paint the sprite onto the surface
  528. if self._NODESPRITE_DATA["surface"] is not None:
  529. rect = self._NODESPRITE_DATA["rect"]
  530. scale = self._NODESPRITE_DATA["scale"]
  531. surf = self._NODESPRITE_DATA["surface"]()
  532. # Of course, only bother if we have a surface to work with.
  533. if surf is not None:
  534. # Do some prescaling work, if needed.
  535. if self._NODESPRITE_DATA["scale_dirty"]:
  536. self._NODESPRITE_UpdateScaleSurface(scale, surf)
  537. fsurf = surf # Initial assumption that the surface is also the "frame"
  538. # If we have a "frame" surface, however, let's get it and blit the rect into the frame surface.
  539. if "frame_surf" in self._NODESPRITE_DATA:
  540. fsurf = self._NODESPRITE_DATA["frame_surf"]
  541. fsurf.blit(surf, (0, 0), rect)
  542. # If scaling, then transform (scale) the frame surface into the scale surface and set the frame surface to the scale surface.
  543. if "scale_surf" in self._NODESPRITE_DATA:
  544. ssurf = self._NODESPRITE_DATA["scale_surf"]
  545. pygame.transform.scale(fsurf, ssurf.get_size(), ssurf)
  546. fsurf = ssurf
  547. # Place the sprite! WHEEEEE!
  548. pos = self.position
  549. surface.blit(fsurf, pos)
  550. # Call on all children
  551. Node._render(self, surface)
  552. def _NODESPRITE_UpdateScaleSurface(self, scale, surf):
  553. self._NODESPRITE_DATA["scale_dirty"] = False
  554. ssurf = None
  555. if "scale_surf" in self._NODESPRITE_DATA:
  556. ssurf = self._NODESPRITE_DATA["scale_surf"]
  557. if scale[0] == 1.0 and scale[1] == 1.0:
  558. if ssurf is not None:
  559. del self._NODESPRITE_DATA["scale_surf"]
  560. return
  561. nw = int(self.rect_width * scale[0])
  562. nh = int(self.rect_height * scale[1])
  563. if nw != ssurf.get_width() or nh != ssurf.get_height():
  564. ssurf = pygame.Surface((nw, nh), pygame.SRCALPHA, surf)
  565. ssurf.fill(pygame.Color(0,0,0,0))
  566. self._NODESPRITE_DATA["scale_surf"] = ssurf
  567. def _NODESPRITE_ValidateRect(self):
  568. if self._NODESPRITE_DATA["surface"] is None:
  569. self._NODESPRITE_DATA["rect"] = [0,0,0,0]
  570. else:
  571. rect = self._NODESPRITE_DATA["rect"]
  572. isize = (self.image_width, self.image_height)
  573. if rect[0] < 0:
  574. rect[2] += rect[0]
  575. rect[0] = 0
  576. elif rect[0] >= isize[0]:
  577. rect[0] = isize[0]-1
  578. rect[2] = 0
  579. if rect[1] < 0:
  580. rect[3] += rect[1]
  581. rect[1] = 0
  582. elif rect[1] >= isize[1]:
  583. rect[1] = isize[1]-1
  584. rect[3] = 0
  585. if rect[2] < 0:
  586. rect[2] = 0
  587. elif rect[0] + rect[2] > isize[0]:
  588. rect[2] = isize[0] - rect[0]
  589. if rect[3] < 0:
  590. rect[3] = 0
  591. elif rect[1] + rect[3] > isize[1]:
  592. rect[3] = isize[1] - rect[1]
  593. fssize = [0,0]
  594. if rect[2] > 0 and rect[3] > 0:
  595. if rect[2] < isize[0] or rect[1] < isize[1]:
  596. surf = self._NODESPRITE_DATA["surface"]()
  597. self._NODESPRITE_DATA["frame_surf"] = pygame.Surface((rect[2], rect[3]), pygame.SRCALPHA, surf)
  598. if fssize[0] > 0 and fssize[1] > 0:
  599. pass
  600. elif "frame_surf" in self._NODESPRITE_DATA:
  601. del self._NODESPRITE_DATA["frame_surf"]