Legend of the Gold Box... A game written for the LOWREZJAM 2018 game jam
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

947 Zeilen
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