Legend of the Gold Box... A game written for the LOWREZJAM 2018 game jam
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

947 lines
31KB

  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. "position":(0,0)
  21. }
  22. if parent is not None:
  23. try:
  24. self.parent = parent
  25. except NodeError as e:
  26. raise e
  27. @property
  28. def parent(self):
  29. return self._NODE_DATA["parent"]
  30. @parent.setter
  31. def parent(self, new_parent):
  32. try:
  33. self.parent_to_node(new_parent)
  34. except NodeError as e:
  35. raise e
  36. @property
  37. def root(self):
  38. if self.parent is None:
  39. return self
  40. return self.parent.root
  41. @property
  42. def name(self):
  43. return self._NODE_DATA["name"]
  44. @name.setter
  45. def name(self, value):
  46. if self.parent is not None:
  47. if self.parent.get_node(value) is not None:
  48. raise NodeError("Parent already contains node named '{}'.".format(name))
  49. self._NODE_DATA["name"] = value
  50. @property
  51. def full_name(self):
  52. if self.parent is None:
  53. return self.name
  54. return self.parent.full_name + "." + self.name
  55. @property
  56. def resource(self):
  57. if self._NODE_DATA["resource"] is None:
  58. # Only bother creating the instance if it's being asked for.
  59. # All ResourceManager instances access same data.
  60. self._NODE_DATA["resource"] = ResourceManager()
  61. return self._NODE_DATA["resource"]
  62. @property
  63. def child_count(self):
  64. return len(self._NODE_DATA["children"])
  65. @property
  66. def position(self):
  67. p = self._NODE_DATA["position"]
  68. return (p[0], p[1])
  69. @position.setter
  70. def position(self, pos):
  71. if not isinstance(pos, (list, tuple)):
  72. raise TypeError("Expected a list or tuple.")
  73. if len(pos) != 2:
  74. raise ValueError("Wrong number of values given.")
  75. try:
  76. self.position_x = pos[0]
  77. self.position_y = pos[1]
  78. except Exception as e:
  79. raise e
  80. @property
  81. def position_x(self):
  82. return self._NODE_DATA["position"][0]
  83. @position_x.setter
  84. def position_x(self, v):
  85. if not isinstance(v, (int, float)):
  86. raise TypeError("Excepted an number value.")
  87. self._NODE_DATA["position"] = (float(v), self._NODE_DATA["position"][1])
  88. @property
  89. def position_y(self):
  90. return self._NODE_DATA["position"][1]
  91. @position_y.setter
  92. def position_y(self, v):
  93. if not isinstance(v, (int, float)):
  94. raise TypeError("Excepted an number value.")
  95. self._NODE_DATA["position"] = (self._NODE_DATA["position"][0], float(v))
  96. def get_world_position(self):
  97. if self.parent is None:
  98. return (0,0)
  99. pos = self.position
  100. ppos = self.parent.get_world_position()
  101. return (pos[0] + ppos[0], pos[1] + ppos[1])
  102. def parent_to_node(self, parent, allow_reparenting=False):
  103. if not isinstance(parent, Node):
  104. raise NodeError("Node may only parent to another Node instance.")
  105. if self.parent is None or self.parent != parent:
  106. if self.parent is not None:
  107. if allow_Reparenting == False:
  108. raise NodeError("Node already assigned a parent Node.")
  109. if self.parent.remove_node(self) != self:
  110. raise NodeError("Failed to remove self from current parent.")
  111. try:
  112. parent.attach_node(self)
  113. except NodeError as e:
  114. raise e
  115. def attach_node(self, node, reparent=False, index=-1):
  116. if node.parent is not None:
  117. if node.parent == self:
  118. return # Nothing to do. Given node already parented to this node.
  119. if reparent == False:
  120. raise NodeError("Node already parented.")
  121. if node.parent.remove_node(node) != node:
  122. raise NodeError("Failed to remove given node from it's current parent.")
  123. if self.get_node(node.name) is not None:
  124. raise NodeError("Node with name '{}' already attached.".format(node.name))
  125. node._NODE_DATA["parent"] = self
  126. children = self._NODE_DATA["children"]
  127. if index < 0 or index >= len(children):
  128. children.append(node)
  129. else:
  130. children.insert(index, node)
  131. def remove_node(self, node):
  132. if isinstance(node, (str, unicode)):
  133. n = self.get_node(node)
  134. if n is not None:
  135. try:
  136. return self.remove_node(n)
  137. except NodeError as e:
  138. raise e
  139. elif isinstance(node, Node):
  140. if node.parent != self:
  141. if node.parent == None:
  142. raise NodeError("Cannot remove an unparented node.")
  143. try:
  144. return node.parent.remove_node(node)
  145. except NodeError as e:
  146. raise e
  147. if node in self._NODE_DATA["children"]:
  148. self._NODE_DATA["children"].remove(node)
  149. node._NODE_DATA["parent"] = None
  150. return node
  151. else:
  152. raise NodeError("Expected a Node instance or a string.")
  153. return None
  154. def get_node(self, name):
  155. if self.child_count <= 0:
  156. return None
  157. subnames = name.split(".")
  158. for c in self._NODE_DATA["children"]:
  159. if c.name == subnames[0]:
  160. if len(subnames) > 1:
  161. return c.get_node(".".join(subnames[1:-1]))
  162. return c
  163. return None
  164. def listen(self, signal, callback_fn):
  165. try:
  166. Events.listen(signal, callback_fn)
  167. except Exception as e:
  168. raise e
  169. def unlisten(self, signal, callback_fn):
  170. Events.unlisten(signal, callback_fn)
  171. def emit(self, signal, data={}):
  172. # NOTE: I'm currently forcing the existance of this "NODE" key in the dictionary given. Not sure if I'll keep this.
  173. data["NODE"] = {
  174. "name":self.name,
  175. "n":self
  176. }
  177. Events.emit(signal, data)
  178. def _init(self):
  179. if hasattr(self, "on_init"):
  180. self.on_init()
  181. for c in self._NODE_DATA["children"]:
  182. c._init()
  183. def _close(self):
  184. if hasattr(self, "on_close"):
  185. self.on_close()
  186. for c in self._NODE_DATA["children"]:
  187. c._close()
  188. def _pause(self):
  189. if hasattr(self, "on_pause"):
  190. self.on_pause()
  191. for c in self._NODE_DATA["children"]:
  192. c._pause()
  193. def _start(self):
  194. if hasattr(self, "on_start"):
  195. self.on_start()
  196. for c in self._NODE_DATA["children"]:
  197. c._start()
  198. def _update(self, dt):
  199. if hasattr(self, "on_update"):
  200. self.on_update(dt)
  201. for c in self._NODE_DATA["children"]:
  202. c._update(dt)
  203. def _render(self, surface):
  204. for c in self._NODE_DATA["children"]:
  205. c._render(surface)
  206. class Node2D(Node):
  207. def __init__(self, name="Node2D", parent=None):
  208. try:
  209. Node.__init__(self, name, parent)
  210. except NodeError as e:
  211. raise e
  212. self._NODE2D_DATA = {
  213. "visible":True
  214. }
  215. @property
  216. def resolution(self):
  217. p = self.parent
  218. # We don't directly have the answer, but maybe our parent does?
  219. if p is not None:
  220. if hasattr(p, "resolution"):
  221. return p.resolution
  222. # Otherwise the Display object should.
  223. return Display.resolution
  224. @property
  225. def visible(self):
  226. return self._NODE2D_DATA["visible"]
  227. @visible.setter
  228. def visible(self, vis):
  229. self._NODE2D_DATA["visible"] = (vis == True)
  230. def _callOnRender(self, surface):
  231. if hasattr(self, "on_render"):
  232. self._ACTIVE_SURF = surface
  233. self.on_render()
  234. del self._ACTIVE_SURF
  235. def _render(self, surface):
  236. if self._NODE2D_DATA["visible"] == True:
  237. self._callOnRender(surface)
  238. Node._render(self, surface)
  239. def draw_image(self, img, pos=(0,0), rect=None):
  240. if not hasattr(self, "_ACTIVE_SURF"):
  241. return
  242. self._ACTIVE_SURF.blit(img, pos, rect)
  243. def fill(self, color):
  244. if not hasattr(self, "_ACTIVE_SURF"):
  245. return
  246. self._ACTIVE_SURF.fill(color)
  247. def draw_lines(self, points, color, thickness=1, closed=False):
  248. if not hasattr(self, "_ACTIVE_SURF"):
  249. return
  250. pygame.draw.lines(self._ACTIVE_SURF, color, closed, points, thickness)
  251. def draw_rect(self, rect, color, thickness=1, fill_color=None):
  252. if not hasattr(self, "_ACTIVE_SURF"):
  253. return
  254. if fill_color is not None:
  255. self._ACTIVE_SURF.fill(fill_color, rect)
  256. if thickness > 0:
  257. pygame.draw.rect(self._ACTIVE_SURF, color, rect, thickness)
  258. def draw_ellipse(self, rect, color, thickness=1, fill_color=None):
  259. if not hasattr(self, "_ACTIVE_SURF"):
  260. return
  261. if fill_color is not None:
  262. pygame.draw.ellipse(self._ACTIVE_SURF, fill_color, rect)
  263. if thickness > 0:
  264. pygame.draw.ellipse(self._ACTIVE_SURF, color, rect, thickness)
  265. def draw_circle(self, pos, radius, color, thickness=1, fill_color=None):
  266. if not hasattr(self, "_ACTIVE_SURF"):
  267. return
  268. if fill_color is not None:
  269. pygame.draw.circle(self._ACTIVE_SURF, fill_color, pos, radius)
  270. if thickness > 0:
  271. pygame.draw.circle(self._ACTIVE_SURF, color, pos, radius, thickness)
  272. def draw_polygon(self, points, color, thickness=1, fill_color=None):
  273. if not hasattr(self, "_ACTIVE_SURF"):
  274. return
  275. if fill_color is not None:
  276. pygame.draw.polygon(self._ACTIVE_SURF, fill_color, points)
  277. if thickness >= 1:
  278. pygame.draw.polygon(self._ACTIVE_SURF, color, points, thickness)
  279. class NodeSurface(Node2D):
  280. def __init__(self, name="NodeSurface", parent=None):
  281. try:
  282. Node2D.__init__(self, name, parent)
  283. except NodeError as e:
  284. raise e
  285. # TODO: Update this class to use the _NODE*_DATA={} structure.
  286. self._NODESURFACE_DATA={
  287. "clear_color":None
  288. }
  289. self._scale = (1.0, 1.0)
  290. self._scaleToDisplay = False
  291. self._scaleDirty = False
  292. self._keepAspectRatio = False
  293. self._alignCenter = False
  294. self._surface = None
  295. self._tsurface = None
  296. self.set_surface()
  297. def _updateTransformSurface(self):
  298. if self._surface is None:
  299. return
  300. self._scaleDirty = False
  301. if self._scaleToDisplay:
  302. dsize = Display.resolution
  303. ssize = self._surface.get_size()
  304. self._scale = (dsize[0] / ssize[0], dsize[1] / ssize[1])
  305. if self._keepAspectRatio:
  306. if self._scale[0] < self._scale[1]:
  307. self._scale = (self._scale[0], self._scale[0])
  308. else:
  309. self._scale = (self._scale[1], self._scale[1])
  310. if self._scale[0] == 1.0 and self._scale[1] == 1.0:
  311. self._tsurface = None
  312. return
  313. size = self._surface.get_size()
  314. nw = size[0] * self._scale[0]
  315. nh = 0
  316. if self._keepAspectRatio:
  317. nh = size[1] * self._scale[0]
  318. else:
  319. nh = size[1] * self._scale[1]
  320. self._tsurface = pygame.Surface((nw, nh), pygame.SRCALPHA, self._surface)
  321. self._tsurface.fill(pygame.Color(0,0,0,0))
  322. @property
  323. def resolution(self):
  324. if self._surface is None:
  325. return super().resolution
  326. return self._surface.get_size()
  327. @resolution.setter
  328. def resolution(self, res):
  329. try:
  330. self.set_surface(res)
  331. except (TypeError, ValueError) as e:
  332. raise e
  333. @property
  334. def width(self):
  335. return self.resolution[0]
  336. @property
  337. def height(self):
  338. return self.resolution[1]
  339. @property
  340. def scale(self):
  341. return self._scale
  342. @scale.setter
  343. def scale(self, scale):
  344. if self._keepAspectRatio:
  345. if not isinstance(scale, (int, float)):
  346. raise TypeError("Expected number value.")
  347. self._scale = (scale, self._scale[1])
  348. else:
  349. if not isinstance(scale, tuple):
  350. raise TypeError("Expected a tuple")
  351. if len(scale) != 2:
  352. raise ValueError("Expected tuple of length two.")
  353. if not isinstance(scale[0], (int, float)) or not isinstance(scale[1], (int, float)):
  354. raise TypeError("Expected number values.")
  355. self._scale = scale
  356. self._updateTransformSurface()
  357. @property
  358. def keep_aspect_ratio(self):
  359. return self._keepAspectRatio
  360. @keep_aspect_ratio.setter
  361. def keep_aspect_ratio(self, keep):
  362. self._keepAspectRatio = (keep == True)
  363. self._updateTransformSurface()
  364. @property
  365. def align_center(self):
  366. return self._alignCenter
  367. @align_center.setter
  368. def align_center(self, center):
  369. self._alignCenter = (center == True)
  370. @property
  371. def scale_to_display(self):
  372. return self._scaleToDisplay
  373. @scale_to_display.setter
  374. def scale_to_display(self, todisplay):
  375. if todisplay == True:
  376. self._scaleToDisplay = True
  377. Events.listen("VIDEORESIZE", self._OnVideoResize)
  378. else:
  379. self._scaleToDisplay = False
  380. Events.unlisten("VIDEORESIZE", self._OnVideoResize)
  381. self._updateTransformSurface()
  382. def scale_to(self, target_resolution):
  383. if self._surface is not None:
  384. size = self._surface.get_size()
  385. nscale = (float(size[0]) / float(target_resolution[0]), float(size[1]) / float(target_resolution[1]))
  386. self.scale = nscale
  387. def set_surface(self, resolution=None):
  388. dsurf = Display.surface
  389. if resolution is None:
  390. if dsurf is not None:
  391. self._surface = dsurf.convert_alpha()
  392. self._surface.fill(pygame.Color(0,0,0,0))
  393. self._updateTransformSurface()
  394. else:
  395. if not isinstance(resolution, tuple):
  396. raise TypeError("Expected a tuple.")
  397. if len(resolution) != 2:
  398. raise ValueError("Expected a tuple of length two.")
  399. if not isinstance(resolution[0], int) or not isinstance(resolution[1], int):
  400. raise TypeError("Tuple expected to contain integers.")
  401. if dsurf is not None:
  402. self._surface = pygame.Surface(resolution, pygame.SRCALPHA, dsurf)
  403. else:
  404. self._surface = pygame.Surface(resolution, pygame.SRCALPHA)
  405. self._surface.fill(pygame.Color(0,0,0,0))
  406. self._updateTransformSurface()
  407. def set_clear_color(self, color):
  408. if color is None:
  409. self._NODESURFACE_DATA["clear_color"] = None
  410. elif isinstance(color, (list, tuple)):
  411. clen = len(color)
  412. if clen == 3 or clen == 4:
  413. iscolor = lambda v: isinstance(v, int) and v >= 0 and v < 256
  414. if iscolor(color[0]) and iscolor(color[1]) and iscolor(color[2]):
  415. if clen == 3 or (clen == 4 and iscolor(color[3])):
  416. self._NODESURFACE_DATA["clear_color"] = pygame.Color(*color)
  417. def get_clear_color(self):
  418. cc = self._NODESURFACE_DATA["clear_color"]
  419. if cc == None:
  420. return None
  421. return (cc.r, cc.g, cc.b, cc.a)
  422. def _render(self, surface):
  423. if self.visible == False:
  424. return
  425. if self._surface is None:
  426. self.set_surface()
  427. if self._surface is not None:
  428. if self._scaleDirty:
  429. self._updateTransformSurface()
  430. cc = self._NODESURFACE_DATA["clear_color"]
  431. if cc is not None:
  432. self._surface.fill(cc)
  433. Node2D._render(self, self._surface)
  434. else:
  435. Node2D._render(self, surface)
  436. self._scale_and_blit(surface)
  437. def _scale_and_blit(self, dest):
  438. dsize = dest.get_size()
  439. src = self._surface
  440. if self._tsurface is not None:
  441. pygame.transform.scale(self._surface, self._tsurface.get_size(), self._tsurface)
  442. src = self._tsurface
  443. ssize = src.get_size()
  444. posx = self.position_x
  445. posy = self.position_y
  446. if self._alignCenter:
  447. if dsize[0] > ssize[0]:
  448. posx += (dsize[0] - ssize[0]) * 0.5
  449. if dsize[1] > ssize[1]:
  450. posy += (dsize[1] - ssize[1]) * 0.5
  451. pos = (int(posx), int(posy))
  452. dest.blit(src, pos)
  453. def _OnVideoResize(self, event, data):
  454. if self._scaleToDisplay:
  455. self._scaleDirty = True
  456. class NodeText(Node2D):
  457. def __init__(self, name="NodeText", parent=None):
  458. try:
  459. Node2D.__init__(self, name, parent)
  460. except NodeError as e:
  461. raise e
  462. self._NODETEXT_DATA={
  463. "font_src":"",
  464. "size":26,
  465. "antialias":True,
  466. "color":pygame.Color(255,255,255),
  467. "background":None,
  468. "text":"Some Text",
  469. "surface":None
  470. }
  471. @property
  472. def font_src(self):
  473. return self._NODETEXT_DATA["font_src"]
  474. @font_src.setter
  475. def font_src(self, src):
  476. res = self.resource
  477. if src != "" and src != self._NODETEXT_DATA["font_src"] and res.is_valid("font", src):
  478. self._NODETEXT_DATA["font_src"] = src
  479. if not res.has("font", src):
  480. res.store("font", src)
  481. self._NODETEXT_DATA["surface"] = None
  482. @property
  483. def size(self):
  484. return self._NODETEXT_DATA["size"]
  485. @size.setter
  486. def size(self, size):
  487. if not isinstance(size, int):
  488. raise TypeError("Expected integer value.")
  489. if size <= 0:
  490. raise ValueError("Size must be greater than zero.")
  491. if size != self._NODETEXT_DATA["size"]:
  492. self._NODETEXT_DATA["size"] = size
  493. self._NODETEXT_DATA["surface"] = None
  494. @property
  495. def antialias(self):
  496. return self._NODETEXT_DATA["antialias"]
  497. @antialias.setter
  498. def antialias(self, enable):
  499. enable = (enable == True)
  500. if enable != self._NODETEXT_DATA["antialias"]:
  501. self._NODETEXT_DATA["antialias"] = enable
  502. self._NODETEXT_DATA["surface"] = None
  503. @property
  504. def text(self):
  505. return self._NODETEXT_DATA["text"]
  506. @text.setter
  507. def text(self, text):
  508. if text != self._NODETEXT_DATA["text"]:
  509. self._NODETEXT_DATA["text"] = text
  510. self._NODETEXT_DATA["surface"] = None
  511. def _setColor(self, cname, r, g, b, a):
  512. if r < 0 or r > 255:
  513. raise ValueError("Red value out of bounds.")
  514. if g < 0 or g > 255:
  515. raise ValueError("Green value out of bounds.")
  516. if b < 0 or b > 255:
  517. raise ValueError("Blue value out of bounds.")
  518. if a < 0 or a > 255:
  519. raise ValueError("Alpha value out of bounds.")
  520. color = self._NODETEXT_DATA[cname]
  521. if color is None or color.r != r or color.g != g or color.b != b or color.a != a:
  522. self._NODETEXT_DATA[cname] = pygame.Color(r,g,b,a)
  523. self._NODETEXT_DATA["surface"] = None
  524. def _getColor(self, cname):
  525. if self._NODETEXT_DATA[cname] is None:
  526. return (0,0,0,0)
  527. else:
  528. c = self._NODETEXT_DATA[cname]
  529. return (c.r, c.g, c.b, c.a)
  530. def set_color(self, r, g, b, a=255):
  531. try:
  532. self._setColor("color", r, g, b, a)
  533. except ValueError as e:
  534. raise e
  535. return self
  536. def get_color(self):
  537. return self._getColor("color")
  538. def set_background(self, r, g, b, a=255):
  539. try:
  540. self._setColor("background", r, g, b, a)
  541. except ValueError as e:
  542. raise e
  543. return self
  544. def clear_background(self):
  545. if self._NODETEXT_DATA["background"] is not None:
  546. self._NODETEXT_DATA["background"] = None
  547. self._NODETEXT_DATA["surface"] = None
  548. return self
  549. def get_background(self):
  550. c = self._getColor("background")
  551. if c[3] <= 0:
  552. return None
  553. return c
  554. def _render(self, surface):
  555. if self.visible == False:
  556. return
  557. if self._NODETEXT_DATA["surface"] is None and self._NODETEXT_DATA["text"] != "":
  558. res = self.resource
  559. fnt = res.get("font", self._NODETEXT_DATA["font_src"], {"size":self._NODETEXT_DATA["size"]})
  560. if fnt is not None and fnt() is not None:
  561. surf = None
  562. try:
  563. text = self.text
  564. antialias = self.antialias
  565. color = self._NODETEXT_DATA["color"]
  566. background = self._NODETEXT_DATA["background"]
  567. surf = fnt().render(text, antialias, color, background)
  568. except pygame.error as e:
  569. pass # TODO: Update to send out warning!
  570. self._NODETEXT_DATA["surface"] = surf
  571. # We really don't want to keep the font instance around... just in case we want to use the same font at different sizes.
  572. res.clear("font", self._NODETEXT_DATA["font_src"])
  573. Node2D._render(self, surface)
  574. if self._NODETEXT_DATA["surface"] is not None:
  575. pos = self.get_world_position()
  576. pos = (int(pos[0]), int(pos[1]))
  577. surface.blit(self._NODETEXT_DATA["surface"], pos)
  578. class NodeSprite(Node2D):
  579. def __init__(self, name="NodeSprite", parent=None):
  580. try:
  581. Node2D.__init__(self, name, parent)
  582. except NodeError as e:
  583. raise e
  584. self._NODESPRITE_DATA={
  585. "rect":[0,0,0,0],
  586. "image":"",
  587. "scale":[1.0, 1.0],
  588. "scale_dirty":True,
  589. "surface":None
  590. }
  591. @property
  592. def image_width(self):
  593. if self._NODESPRITE_DATA["surface"] is None:
  594. return 0
  595. surf = self._NODESPRITE_DATA["surface"]()
  596. return surf.get_width()
  597. @property
  598. def image_height(self):
  599. if self._NODESPRITE_DATA["surface"] is None:
  600. return 0
  601. surf = self._NODESPRITE_DATA["surface"]()
  602. return surf.get_height()
  603. @property
  604. def sprite_width(self):
  605. return int(self.rect_width * self.scale_x)
  606. @property
  607. def sprite_height(self):
  608. return int(self.rect_height * self.scale_y)
  609. @property
  610. def rect(self):
  611. return (self._NODESPRITE_DATA["rect"][0],
  612. self._NODESPRITE_DATA["rect"][1],
  613. self._NODESPRITE_DATA["rect"][2],
  614. self._NODESPRITE_DATA["rect"][3])
  615. @rect.setter
  616. def rect(self, rect):
  617. if not isinstance(rect, (list, tuple)):
  618. raise TypeError("Expected a list or tuple.")
  619. if len(rect) != 4:
  620. raise ValueError("rect value contains wrong number of values.")
  621. try:
  622. self.rect_x = rect[0]
  623. self.rect_y = rect[1]
  624. self.rect_width = rect[2]
  625. self.rect_height = rect[3]
  626. except Exception as e:
  627. raise e
  628. @property
  629. def rect_x(self):
  630. return self._NODESPRITE_DATA["rect"][0]
  631. @rect_x.setter
  632. def rect_x(self, v):
  633. if not isinstance(v, int):
  634. raise TypeError("Expected integer value.")
  635. self._NODESPRITE_DATA["rect"][0] = v
  636. self._NODESPRITE_ValidateRect()
  637. @property
  638. def rect_y(self):
  639. return self._NODESPRITE_DATA["rect"][1]
  640. @rect_y.setter
  641. def rect_y(self, v):
  642. if not isinstance(v, int):
  643. raise TypeError("Expected integer value.")
  644. self._NODESPRITE_DATA["rect"][1] = v
  645. self._NODESPRITE_ValidateRect()
  646. @property
  647. def rect_width(self):
  648. return self._NODESPRITE_DATA["rect"][2]
  649. @rect_width.setter
  650. def rect_width(self, v):
  651. if not isinstance(v, int):
  652. raise TypeError("Expected integer value.")
  653. self._NODESPRITE_DATA["rect"][2] = v
  654. self._NODESPRITE_ValidateRect()
  655. @property
  656. def rect_height(self):
  657. return self._NODESPRITE_DATA["rect"][3]
  658. @rect_height.setter
  659. def rect_height(self, v):
  660. if not isinstance(v, int):
  661. raise TypeError("Expected integer value.")
  662. self._NODESPRITE_DATA["rect"][3] = v
  663. self._NODESPRITE_ValidateRect()
  664. @property
  665. def center(self):
  666. r = self._NODESPRITE_DATA["rect"]
  667. return (int(r[0] + (r[2] * 0.5)), int(r[1] + (r[3] * 0.5)))
  668. @property
  669. def scale(self):
  670. return (self._NODESPRITE_DATA["scale"][0], self._NODESPRITE_DATA["scale"][1])
  671. @scale.setter
  672. def scale(self, scale):
  673. if not isinstance(scale, (list, tuple)):
  674. raise TypeError("Expected a list or tuple.")
  675. if len(scale) != 2:
  676. raise ValueError("Scale contains wrong number of values.")
  677. try:
  678. self.scale_x = scale[0]
  679. self.scale_y = scale[1]
  680. except Exception as e:
  681. raise e
  682. @property
  683. def scale_x(self):
  684. return self._NODESPRITE_DATA["scale"][0]
  685. @scale_x.setter
  686. def scale_x(self, v):
  687. if not isinstance(v, (int, float)):
  688. raise TypeError("Expected number value.")
  689. self._NODESPRITE_DATA["scale"][0] = float(v)
  690. self._NODESPRITE_DATA["scale_dirty"] = True
  691. @property
  692. def scale_y(self):
  693. return self._NODESPRITE_DATA["scale"][1]
  694. @scale_y.setter
  695. def scale_y(self, v):
  696. if not isinstance(v, (int, float)):
  697. raise TypeError("Expected number value.")
  698. self._NODESPRITE_DATA["scale"][1] = float(v)
  699. self._NODESPRITE_DATA["scale_dirty"] = True
  700. @property
  701. def image(self):
  702. return self._NODESPRITE_DATA["image"]
  703. @image.setter
  704. def image(self, src):
  705. src = src.strip()
  706. if self._NODESPRITE_DATA["image"] == src:
  707. return # Nothing to change... lol
  708. if self._NODESPRITE_DATA["image"] != "":
  709. self._NODESPRITE_DATA["surface"] = None # Clear reference to original surface.
  710. if src != "":
  711. rm = self.resource
  712. try:
  713. if not rm.has("graphic", src):
  714. rm.store("graphic", src)
  715. self._NODESPRITE_DATA["image"] = src
  716. self._NODESPRITE_DATA["surface"] = rm.get("graphic", src)
  717. if self._NODESPRITE_DATA["surface"] is None:
  718. self._NODESPRITE_DATA["image"] = ""
  719. self._NODESPRITE_DATA["rect"]=[0,0,0,0]
  720. else:
  721. # Resetting the rect to identity for the new image.
  722. surf = self._NODESPRITE_DATA["surface"]()
  723. size = surf.get_size()
  724. self._NODESPRITE_DATA["rect"]=[0,0,size[0], size[1]]
  725. except Exception as e:
  726. raise e
  727. else:
  728. self._NODESPRITE_DATA["image"] = ""
  729. self._NODESPRITE_DATA["rect"]=[0,0,0,0]
  730. def _render(self, surface):
  731. if self.visible == False:
  732. return
  733. # Call the on_render() method, if any
  734. Node2D._callOnRender(self, surface)
  735. # Paint the sprite onto the surface
  736. if self._NODESPRITE_DATA["surface"] is not None:
  737. rect = self._NODESPRITE_DATA["rect"]
  738. scale = self._NODESPRITE_DATA["scale"]
  739. surf = self._NODESPRITE_DATA["surface"]()
  740. # Of course, only bother if we have a surface to work with.
  741. if surf is not None:
  742. # Do some prescaling work, if needed.
  743. if self._NODESPRITE_DATA["scale_dirty"]:
  744. self._NODESPRITE_DATA["scale_dirty"] = False
  745. self._NODESPRITE_UpdateFrameSurface(surf)
  746. self._NODESPRITE_UpdateScaleSurface(scale, surf)
  747. fsurf = surf # Initial assumption that the surface is also the "frame"
  748. # If we have a "frame" surface, however, let's get it and blit the rect into the frame surface.
  749. if "frame_surf" in self._NODESPRITE_DATA:
  750. fsurf = self._NODESPRITE_DATA["frame_surf"]
  751. fsurf.blit(surf, (0, 0), rect)
  752. # If scaling, then transform (scale) the frame surface into the scale surface and set the frame surface to the scale surface.
  753. if "scale_surf" in self._NODESPRITE_DATA:
  754. ssurf = self._NODESPRITE_DATA["scale_surf"]
  755. pygame.transform.scale(fsurf, ssurf.get_size(), ssurf)
  756. fsurf = ssurf
  757. # Place the sprite! WHEEEEE!
  758. pos = self.get_world_position()
  759. surface.blit(fsurf, pos)
  760. # Call on all children
  761. Node._render(self, surface)
  762. def _NODESPRITE_UpdateFrameSurface(self, surf):
  763. rect = self.rect
  764. ssize = surf.get_size()
  765. if rect[2] == ssize[0] and rect[3] == ssize[1]:
  766. if "frame_surf" in self._NODESPRITE_DATA:
  767. del self._NODESPRITE_DATA
  768. return
  769. if "frame_surf" not in self._NODESPRITE_DATA:
  770. self._NODESPRITE_DATA["frame_surf"] = pygame.Surface((rect[2], rect[3]), pygame.SRCALPHA, surf)
  771. else:
  772. fsurf = self._NODESPRITE_DATA["frame_surf"]
  773. fsize = fsurf.get_size()
  774. if fsize[0] != rect[2] or fsize[1] != rect[3]:
  775. self._NODESPRITE_DATA["frame_surf"] = pygame.Surface((rect[2], rect[3]), pygame.SRCALPHA, surf)
  776. def _NODESPRITE_UpdateScaleSurface(self, scale, surf):
  777. ssurf = None
  778. if "scale_surf" in self._NODESPRITE_DATA:
  779. ssurf = self._NODESPRITE_DATA["scale_surf"]
  780. if scale[0] == 1.0 and scale[1] == 1.0:
  781. if ssurf is not None:
  782. del self._NODESPRITE_DATA["scale_surf"]
  783. return
  784. nw = int(self.rect_width * scale[0])
  785. nh = int(self.rect_height * scale[1])
  786. if nw != ssurf.get_width() or nh != ssurf.get_height():
  787. ssurf = pygame.Surface((nw, nh), pygame.SRCALPHA, surf)
  788. ssurf.fill(pygame.Color(0,0,0,0))
  789. self._NODESPRITE_DATA["scale_surf"] = ssurf
  790. def _NODESPRITE_ValidateRect(self):
  791. if self._NODESPRITE_DATA["surface"] is None:
  792. self._NODESPRITE_DATA["rect"] = [0,0,0,0]
  793. else:
  794. rect = self._NODESPRITE_DATA["rect"]
  795. isize = (self.image_width, self.image_height)
  796. if rect[0] < 0:
  797. rect[2] += rect[0]
  798. rect[0] = 0
  799. elif rect[0] >= isize[0]:
  800. rect[0] = isize[0]-1
  801. rect[2] = 0
  802. if rect[1] < 0:
  803. rect[3] += rect[1]
  804. rect[1] = 0
  805. elif rect[1] >= isize[1]:
  806. rect[1] = isize[1]-1
  807. rect[3] = 0
  808. if rect[2] < 0:
  809. rect[2] = 0
  810. elif rect[0] + rect[2] > isize[0]:
  811. rect[2] = isize[0] - rect[0]
  812. if rect[3] < 0:
  813. rect[3] = 0
  814. elif rect[1] + rect[3] > isize[1]:
  815. rect[3] = isize[1] - rect[1]
  816. self._NODESPRITE_DATA["scale_dirty"] = True