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.

549 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 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. def _render(self, surface):
  141. Node._render(self, surface)
  142. if hasattr(self, "on_render"):
  143. self._ACTIVE_SURF = surface
  144. self.on_render()
  145. del self._ACTIVE_SURF
  146. def draw_image(self, img, pos=(0,0), rect=None):
  147. if not hasattr(self, "_ACTIVE_SURF"):
  148. return
  149. self._ACTIVE_SURF.blit(img, pos, rect)
  150. def fill(self, color):
  151. if not hasattr(self, "_ACTIVE_SURF"):
  152. return
  153. self._ACTIVE_SURF.fill(color)
  154. def draw_lines(self, points, color, thickness=1, closed=False):
  155. if not hasattr(self, "_ACTIVE_SURF"):
  156. return
  157. pygame.draw.lines(self._ACTIVE_SURF, color, closed, points, thickness)
  158. def draw_rect(self, rect, color, thickness=1):
  159. if not hasattr(self, "_ACTIVE_SURF"):
  160. return
  161. pygame.draw.rect(self._ACTIVE_SURF, color, rect, thickness)
  162. def draw_ellipse(self, rect, 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.ellipse(self._ACTIVE_SURF, fill_color, rect)
  167. if thickness > 0:
  168. pygame.draw.ellipse(self._ACTIVE_SURF, color, rect, thickness)
  169. def draw_circle(self, pos, radius, 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.circle(self._ACTIVE_SURF, fill_color, pos, radius)
  174. if thickness > 0:
  175. pygame.draw.circle(self._ACTIVE_SURF, color, pos, radius, thickness)
  176. def draw_polygon(self, points, color, thickness=1, fill_color=None):
  177. if not hasattr(self, "_ACTIVE_SURF"):
  178. return
  179. if fill_color is not None:
  180. pygame.draw.polygon(self._ACTIVE_SURF, fill_color, points)
  181. if thickness >= 1:
  182. pygame.draw.polygon(self._ACTIVE_SURF, color, points, thickness)
  183. class NodeSurface(Node2D):
  184. def __init__(self, name="NodeSurface", parent=None):
  185. try:
  186. Node2D.__init__(self, name, parent)
  187. except NodeError as e:
  188. raise e
  189. # TODO: Update this class to use the _NODE*_DATA={} structure.
  190. self._offset = (0.0, 0.0)
  191. self._scale = (1.0, 1.0)
  192. self._scaleToDisplay = False
  193. self._scaleDirty = False
  194. self._keepAspectRatio = False
  195. self._alignCenter = False
  196. self._surface = None
  197. self._tsurface = None
  198. self.set_surface()
  199. def _updateTransformSurface(self):
  200. if self._surface is None:
  201. return
  202. self._scaleDirty = False
  203. if self._scaleToDisplay:
  204. dsize = Display.resolution
  205. ssize = self._surface.get_size()
  206. self._scale = (dsize[0] / ssize[0], dsize[1] / ssize[1])
  207. if self._keepAspectRatio:
  208. if self._scale[0] < self._scale[1]:
  209. self._scale = (self._scale[0], self._scale[0])
  210. else:
  211. self._scale = (self._scale[1], self._scale[1])
  212. if self._scale[0] == 1.0 and self._scale[1] == 1.0:
  213. self._tsurface = None
  214. return
  215. size = self._surface.get_size()
  216. nw = size[0] * self._scale[0]
  217. nh = 0
  218. if self._keepAspectRatio:
  219. nh = size[1] * self._scale[0]
  220. else:
  221. nh = size[1] * self._scale[1]
  222. self._tsurface = pygame.Surface((nw, nh), pygame.SRCALPHA, self._surface)
  223. self._tsurface.fill(pygame.Color(0,0,0,0))
  224. @property
  225. def resolution(self):
  226. if self._surface is None:
  227. return (0,0)
  228. return self._surface.get_size()
  229. @resolution.setter
  230. def resolution(self, res):
  231. try:
  232. self.set_surface(res)
  233. except (TypeError, ValueError) as e:
  234. raise e
  235. @property
  236. def width(self):
  237. return self.resolution[0]
  238. @property
  239. def height(self):
  240. return self.resolution[1]
  241. @property
  242. def offset(self):
  243. return self._offset
  244. @offset.setter
  245. def offset(self, offset):
  246. if not isinstance(offset, tuple):
  247. raise TypeError("Expected a tuple")
  248. if len(offset) != 2:
  249. raise ValueError("Expected tuple of length two.")
  250. if not isinstance(offset[0], (int, float)) or not isinstance(offset[1], (int, float)):
  251. raise TypeError("Expected number values.")
  252. self._offset = (float(offset[0]), float(offset[1]))
  253. @property
  254. def offset_x(self):
  255. return self._offset[0]
  256. @offset_x.setter
  257. def offset_x(self, x):
  258. if not isinstance(x, (int, float)):
  259. raise TypeError("Expected number value.")
  260. self._offset = (x, self._offset[1])
  261. @property
  262. def offset_y(self):
  263. return self._offset[1]
  264. @offset_y.setter
  265. def offset_y(self, y):
  266. if not isinstance(y, (int, float)):
  267. raise TypeError("Expected number value.")
  268. self._offset = (self._offset[0], y)
  269. @property
  270. def scale(self):
  271. return self._scale
  272. @scale.setter
  273. def scale(self, scale):
  274. if self._keepAspectRatio:
  275. if not isinstance(scale, (int, float)):
  276. raise TypeError("Expected number value.")
  277. self._scale = (scale, self._scale[1])
  278. else:
  279. if not isinstance(scale, tuple):
  280. raise TypeError("Expected a tuple")
  281. if len(scale) != 2:
  282. raise ValueError("Expected tuple of length two.")
  283. if not isinstance(scale[0], (int, float)) or not isinstance(scale[1], (int, float)):
  284. raise TypeError("Expected number values.")
  285. self._scale = scale
  286. self._updateTransformSurface()
  287. @property
  288. def keep_aspect_ratio(self):
  289. return self._keepAspectRatio
  290. @keep_aspect_ratio.setter
  291. def keep_aspect_ratio(self, keep):
  292. self._keepAspectRatio = (keep == True)
  293. self._updateTransformSurface()
  294. @property
  295. def align_center(self):
  296. return self._alignCenter
  297. @align_center.setter
  298. def align_center(self, center):
  299. self._alignCenter = (center == True)
  300. @property
  301. def scale_to_display(self):
  302. return self._scaleToDisplay
  303. @scale_to_display.setter
  304. def scale_to_display(self, todisplay):
  305. if todisplay == True:
  306. self._scaleToDisplay = True
  307. Events.listen("VIDEORESIZE", self._OnVideoResize)
  308. else:
  309. self._scaleToDisplay = False
  310. Events.unlisten("VIDEORESIZE", self._OnVideoResize)
  311. self._updateTransformSurface()
  312. def scale_to(self, target_resolution):
  313. if self._surface is not None:
  314. size = self._surface.get_size()
  315. nscale = (float(size[0]) / float(target_resolution[0]), float(size[1]) / float(target_resolution[1]))
  316. self.scale = nscale
  317. def set_surface(self, resolution=None):
  318. dsurf = Display.surface
  319. if resolution is None:
  320. if dsurf is not None:
  321. self._surface = dsurf.convert_alpha()
  322. self._surface.fill(pygame.Color(0,0,0,0))
  323. self._updateTransformSurface()
  324. else:
  325. if not isinstance(resolution, tuple):
  326. raise TypeError("Expected a tuple.")
  327. if len(resolution) != 2:
  328. raise ValueError("Expected a tuple of length two.")
  329. if not isinstance(resolution[0], int) or not isinstance(resolution[1], int):
  330. raise TypeError("Tuple expected to contain integers.")
  331. if dsurf is not None:
  332. self._surface = pygame.Surface(resolution, pygame.SRCALPHA, dsurf)
  333. else:
  334. self._surface = pygame.Surface(resolution, pygame.SRCALPHA)
  335. self._surface.fill(pygame.Color(0,0,0,0))
  336. self._updateTransformSurface()
  337. def _render(self, surface):
  338. if self._surface is None:
  339. self.set_surface()
  340. if self._surface is not None:
  341. if self._scaleDirty:
  342. self._updateTransformSurface()
  343. Node2D._render(self, self._surface)
  344. else:
  345. Node2D._render(self, surface)
  346. self._scale_and_blit(surface)
  347. def _scale_and_blit(self, dest):
  348. dsize = dest.get_size()
  349. src = self._surface
  350. if self._tsurface is not None:
  351. pygame.transform.scale(self._surface, self._tsurface.get_size(), self._tsurface)
  352. src = self._tsurface
  353. ssize = src.get_size()
  354. posx = self._offset[0]
  355. posy = self._offset[1]
  356. if self._alignCenter:
  357. if dsize[0] > ssize[0]:
  358. posx += (dsize[0] - ssize[0]) * 0.5
  359. if dsize[1] > ssize[1]:
  360. posy += (dsize[1] - ssize[1]) * 0.5
  361. pos = (int(posx), int(posy))
  362. dest.blit(src, pos)
  363. def _OnVideoResize(self, event, data):
  364. if self._scaleToDisplay:
  365. self._scaleDirty = True
  366. class NodeSprite(Node2D):
  367. def __init__(self, name="NodeSprite", parent=None):
  368. try:
  369. Node2D.__init__(self, name, parent)
  370. except NodeError as e:
  371. raise e
  372. self._NODESPRITE_DATA={
  373. "rect":[0,0,0,0],
  374. "image":"",
  375. "scale":[1.0, 1.0],
  376. "surface":None
  377. }
  378. @property
  379. def rect(self):
  380. return (self._NODESPRITE_DATA["rect"][0],
  381. self._NODESPRITE_DATA["rect"][1],
  382. self._NODESPRITE_DATA["rect"][2],
  383. self._NODESPRITE_DATA["rect"][3])
  384. @rect.setter
  385. def rect(self, rect):
  386. if not isinstance(rect, (list, tuple)):
  387. raise TypeError("Expected a list or tuple.")
  388. if len(rect) != 4:
  389. raise ValueError("rect value contains wrong number of values.")
  390. try:
  391. self.rect_x = rect[0]
  392. self.rect_y = rect[1]
  393. self.rect_width = rect[2]
  394. self.rect_height = rect[3]
  395. except Exception as e:
  396. raise e
  397. @property
  398. def rect_x(self):
  399. return self._NODESPRITE_DATA["rect"][0]
  400. @rect_x.setter
  401. def rect_x(self, v):
  402. if not isinstance(v, int):
  403. raise TypeError("Expected integer value.")
  404. self._NODESPRITE_DATA["rect"][0] = v
  405. @property
  406. def rect_y(self):
  407. return self._NODESPRITE_DATA["rect"][1]
  408. @rect_y.setter
  409. def rect_y(self, v):
  410. if not isinstance(v, int):
  411. raise TypeError("Expected integer value.")
  412. self._NODESPRITE_DATA["rect"][1] = v
  413. @property
  414. def rect_width(self):
  415. return self._NODESPRITE_DATA["rect"][2]
  416. @rect_width.setter
  417. def rect_width(self, v):
  418. if not isinstance(v, int):
  419. raise TypeError("Expected integer value.")
  420. self._NODESPRITE_DATA["rect"][2] = v
  421. @property
  422. def rect_height(self):
  423. return self._NODESPRITE_DATA["rect"][3]
  424. @rect_height.setter
  425. def rect_height(self, v):
  426. if not isinstance(v, int):
  427. raise TypeError("Expected integer value.")
  428. self._NODESPRITE_DATA["rect"][3] = v
  429. @property
  430. def center(self):
  431. r = self._NODESPRITE_DATA["rect"]
  432. return (int(r[0] + (r[2] * 0.5)), int(r[1] + (r[3] * 0.5)))
  433. @property
  434. def scale(self):
  435. return (self._NODESPRITE_DATA["scale"][0], self._NODESPRITE_DATA["scale"][1])
  436. @scale.setter(self, scale):
  437. if not isinstance(scale, (list, tuple)):
  438. raise TypeError("Expected a list or tuple.")
  439. if len(scale) != 2:
  440. raise ValueError("Scale contains wrong number of values.")
  441. try:
  442. self.scale_x = scale[0]
  443. self.scale_y = scale[1]
  444. except Exception as e:
  445. raise e
  446. @property
  447. def scale_x(self):
  448. return self._NODESPRITE_DATA["scale"][0]
  449. @scale_x.setter
  450. def scale_x(self, v):
  451. if not isinstance(v, (int, float)):
  452. raise TypeError("Expected number value.")
  453. self._NODESPRITE_DATA["scale"][0] = float(v)
  454. @property
  455. def scale_y(self):
  456. return self._NODESPRITE_DATA["scale"][1]
  457. @scale_y.setter
  458. def scale_y(self, v):
  459. if not isinstance(v, (int, float)):
  460. raise TypeError("Expected number value.")
  461. self._NODESPRITE_DATA["scale"][1] = float(v)
  462. @property
  463. def image(self):
  464. return self._NODESPRITE_DATA["image"]
  465. @image.setter
  466. def image(self, image):
  467. if self._NODESPRITE_DATA["image"] != "":
  468. self._NODESPRITE_DATA["surface"] = None # Clear reference to original surface.
  469. pass