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.

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