TechSkylander1518

UI Flair Tutorial

Nov 4th, 2023 (edited)
104
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 35.02 KB | None | 0 0
  1. I wanted to expand a bit on Marin's GUI guide with some tricks you can use to add a little polish to your UI!
  2.  
  3. A bit of clarification here - I'm not a graphic designer, so my focus here is more on how to /implement/ these little details, and not necessarily deciding where/if they should be added to begin with. (It's like I'm showing you how to hammer in nails and turn screws, but not how to draw blueprints, if that analogy makes any sense) I do generally think that all of these additions improve on the base design, but mostly just in the sense of "I think it looks nice", haha. (I'll clarify a bit more where I think they improve beyond just aesthetics)
  4.  
  5. https://reliccastle.com/resources/98/
  6.  
  7. I'm adding these in one by one and labelling them as different "versions" so you can see the progression, but I'm not doing them in any particular order. (There's no reason you couldn't make the v3 change before the v2 change, for example)
  8.  
  9. v1 - Base version
  10.  
  11. [code=Ruby]
  12. class BooleanUI
  13. def initialize
  14. @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
  15. @sprites = {}
  16. @sel = 0
  17. @sprites["bg"] = Sprite.new(@viewport)
  18. @sprites["bg"].bitmap = Bitmap.new("Graphics/UI/Boolean/bg")
  19. @sprites["yes"] = Sprite.new(@viewport)
  20. @sprites["yes"].bitmap = Bitmap.new("Graphics/UI/Boolean/yes")
  21. @sprites["yes"].x = 192
  22. @sprites["yes"].y = 130
  23. @sprites["no"] = Sprite.new(@viewport)
  24. @sprites["no"].bitmap = Bitmap.new("Graphics/UI/Boolean/no")
  25. @sprites["no"].x = 192
  26. @sprites["no"].y = 210
  27. @sprites["sel"] = Sprite.new(@viewport)
  28. @sprites["sel"].bitmap = Bitmap.new("Graphics/UI/Boolean/cursor")
  29. @sprites["sel"].x = 192
  30. @sprites["sel"].y = 130
  31. main
  32. end
  33.  
  34. def main
  35. loop do
  36. Graphics.update
  37. Input.update
  38. if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN)
  39. pbPlayCursorSE
  40. @sel = (@sel+1)%2
  41. @sprites["sel"].y = 130 + (80 * @sel)
  42. end
  43. break if Input.trigger?(Input::BACK)
  44. end
  45. dispose
  46. end
  47.  
  48. def dispose
  49. pbDisposeSpriteHash(@sprites)
  50. @viewport.dispose
  51. end
  52. end
  53. [/code]
  54.  
  55. This is mainly just to let you see what we're starting with! The code is taken straight from Marin's tutorial, I've just made a few changes -
  56. -I've changed what images are being displayed and what coordinates they're at. (as well as some internal stuff like the class name and hash IDs) That sounds like a lot, and when you see it in-game, it definitely is - this is a totally different screen from Marin's, after all. But from a code perspective, it's not all that different - instead of telling it to display image Z at coordinates X,Y, I'm telling it to display image A at coordinate A,B. Everything's mostly following the same format!
  57. ((Add text-compare screenshot
  58. -I've commented out the code for pressing Enter for now, just because I want to save that for later in the tutorial
  59. -I removed Kernel., since Kernel. isn't used in Essentials anymore. (I think it was phased out in v18?)
  60. -Here's the more interesting one - I've added wraparound!
  61.  
  62. Wraparound is where you can loop back to the start of a menu from the end of it.
  63. ((Get example in PC)
  64. In Marin's initial script, you can press left to move left from girl to boy, and press right to move right from boy to girl.
  65. ((Probably get screencap illustrating it) But you couldn't press left to move from boy to girl, or right to move from girl to boy.
  66. ((Get screencap)
  67.  
  68. For GUI with only two choices like these, wraparound isn't really amazing - you're only pressing one key either way. But for GUI where there's a longer list, it's nice to be able to just loop back to the start rather than scroll back through a bunch of choices!
  69.  
  70. ((Show summary example
  71.  
  72. v2 - Changing value directly (introducing opacity change)
  73. [CODE lang="ruby" highlight="16, 32-38, 41-47"]
  74. class BooleanUI
  75. def initialize
  76. @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
  77. @sprites = {}
  78. @sel = 0
  79. @sprites["bg"] = Sprite.new(@viewport)
  80. @sprites["bg"].bitmap = Bitmap.new("Graphics/UI/Boolean/bg")
  81. @sprites["yes"] = Sprite.new(@viewport)
  82. @sprites["yes"].bitmap = Bitmap.new("Graphics/UI/Boolean/yes")
  83. @sprites["yes"].x = 192
  84. @sprites["yes"].y = 130
  85. @sprites["no"] = Sprite.new(@viewport)
  86. @sprites["no"].bitmap = Bitmap.new("Graphics/UI/Boolean/no")
  87. @sprites["no"].x = 192
  88. @sprites["no"].y = 210
  89. @sprites["no"].opacity = 150
  90. @sprites["sel"] = Sprite.new(@viewport)
  91. @sprites["sel"].bitmap = Bitmap.new("Graphics/UI/Boolean/cursor")
  92. @sprites["sel"].x = 192
  93. @sprites["sel"].y = 130
  94. main
  95. end
  96.  
  97. def main
  98. loop do
  99. Graphics.update
  100. Input.update
  101. if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN)
  102. pbPlayCursorSE
  103. @sel = (@sel+1)%2
  104. @sprites["sel"].y = 130 + (80 * @sel)
  105. if @sel == 0
  106. @sprites["yes"].opacity = 255
  107. @sprites["no"].opacity = 150
  108. else
  109. @sprites["yes"].opacity = 150
  110. @sprites["no"].opacity = 255
  111. end
  112. end
  113. break if Input.trigger?(Input::BACK)
  114. #if Input.trigger?(Input::C)
  115. # cmd = pbConfirmMessage(_INTL("Are you sure you're a #{@sel == 0 ? "boy" : "girl"}?"))
  116. # if cmd
  117. # pbChangePlayer(@sel)
  118. # break
  119. # end
  120. #end
  121. end
  122. dispose
  123. end
  124.  
  125. def dispose
  126. pbDisposeSpriteHash(@sprites)
  127. @viewport.dispose
  128. end
  129. end
  130. [/code]
  131. A nice, easy addition - when the cursor moves, the button that isn't selected will become slightly transparent, while the button that is selected will become fully opaque. I personally think this is helpful to more clearly communicate to the player which option is currently selected.
  132.  
  133. If you've used the Show Picture/Move Picture commands in events, you'll be familiar with the different properties of an image that you can edit. With event commands, this includes
  134. -Coordinates
  135. -X-Zoom and Y-Zoom
  136. -Opacity (how transparent/solid an image is) - 0 is fully transparent, 255 is fully opaque.
  137. -Blend type
  138. -Tone
  139. You can also use the Rotate Picture and Change Picture Color Tone commands to change the angle and tone, respectively.
  140.  
  141. With scripting, you can do the same things! It's just going to be done through code rather than event commands. Sometimes, this is a little harder - it's a lot nicer setting the picture tone via event, where you can see what the tone looks like. But a lot of times it offers new possibilities, because you can add math functions that you couldn't do via events! A common one is checking Graphics.width or Graphics.height to position images based on the screen size, rather than getting the exact width/height and working from there. Graphics.width/2 is a little easier to remember than "256, or whatever half of what my customer screen size is", and it's easier to adapt to other screen sizes, too!
  142.  
  143. There's a few other attributes of a Sprite object that aren't done with the usual Game_Picture class, stuff like color (different from tone), flash, and wave. I'm not as familiar with these concepts, but you can read the Ruby documentation about them here.
  144. ((add link
  145. https://www.rubydoc.info/gems/openrgss/Sprite
  146.  
  147. Note that here, I'm setting the values immediately - the images aren't fading in/out, they're just immediately being set to the new value. It's just like how the cursor moves - you don't see it moving to the new position, it just immediately appears there. But what if I wanted to show it making that transition? In an event, I'd set the number of frames to move it, but as a script, I'm going to have to take a different approach.
  148.  
  149. v3 - Changing value over time (adding moving cursor)
  150.  
  151. [CODE lang="ruby" highlight="21, 29-33, 37"]
  152. class BooleanUI
  153. def initialize
  154. @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
  155. @sprites = {}
  156. @sel = 0
  157. @sprites["bg"] = Sprite.new(@viewport)
  158. @sprites["bg"].bitmap = Bitmap.new("Graphics/UI/Boolean/bg")
  159. @sprites["yes"] = Sprite.new(@viewport)
  160. @sprites["yes"].bitmap = Bitmap.new("Graphics/UI/Boolean/yes")
  161. @sprites["yes"].x = 192
  162. @sprites["yes"].y = 130
  163. @sprites["no"] = Sprite.new(@viewport)
  164. @sprites["no"].bitmap = Bitmap.new("Graphics/UI/Boolean/no")
  165. @sprites["no"].x = 192
  166. @sprites["no"].y = 210
  167. @sprites["no"].opacity = 150
  168. @sprites["sel"] = Sprite.new(@viewport)
  169. @sprites["sel"].bitmap = Bitmap.new("Graphics/UI/Boolean/cursor")
  170. @sprites["sel"].x = 192
  171. @sprites["sel"].y = 130
  172. @selgoal = 130
  173. main
  174. end
  175.  
  176. def main
  177. loop do
  178. Graphics.update
  179. Input.update
  180. if @sprites["sel"].y != @selgoal
  181. dif = 5
  182. dif *= -1 if @sprites["sel"].y > @selgoal
  183. @sprites["sel"].y += dif
  184. end
  185. if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN)
  186. pbPlayCursorSE
  187. @sel = (@sel+1)%2
  188. @selgoal = 130 + (80 * @sel)
  189. if @sel == 0
  190. @sprites["yes"].opacity = 255
  191. @sprites["no"].opacity = 150
  192. else
  193. @sprites["yes"].opacity = 150
  194. @sprites["no"].opacity = 255
  195. end
  196. end
  197. break if Input.trigger?(Input::BACK)
  198. #if Input.trigger?(Input::C)
  199. # cmd = pbConfirmMessage(_INTL("Are you sure you're a #{@sel == 0 ? "boy" : "girl"}?"))
  200. # if cmd
  201. # pbChangePlayer(@sel)
  202. # break
  203. # end
  204. #end
  205. end
  206. dispose
  207. end
  208.  
  209. def dispose
  210. pbDisposeSpriteHash(@sprites)
  211. @viewport.dispose
  212. end
  213. end
  214. [/code]
  215. Be sure to cover - making sure the dif is divisible, could make pbUpdate its own method
  216.  
  217. Here, I've changed the code so that when the cursor is moved, you see it sliding to its new position, rather than have it just appear at its new coordinates. That's another nice feature of changing these properties via script - we can directly add/subtract/multiply/etc these values, rather than setting them to specific numbers each time. So in this case, rather than tell the game "Change sel's y value to 130, then to 135, then to 140, then to 145...", I just tell it "add 5 to sel's y value until it gets to 210".
  218.  
  219. The most important thing to remember is to run Graphics.update after the change is applied, so it'll actually be displayed. Beyond that, you've got a variety of ways you can go about this, depending on how you want your code arranged. I'm not going to cover every possible method, but I'll briefly show an alternative way this could be done, and explain my reasoning behind why I've arranged my code like this.
  220.  
  221. Here's a version of the code where this is done via its own loop and only uses constants, rather than using variables like dif and @selgoal.
  222.  
  223.  
  224. [CODE lang="ruby" highlight="34-37, 41-44"]
  225. class BooleanUI
  226. def initialize
  227. @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
  228. @sprites = {}
  229. @sel = 0
  230. @sprites["bg"] = Sprite.new(@viewport)
  231. @sprites["bg"].bitmap = Bitmap.new("Graphics/UI/Boolean/bg - Copy")
  232. @sprites["yes"] = Sprite.new(@viewport)
  233. @sprites["yes"].bitmap = Bitmap.new("Graphics/UI/Boolean/yes")
  234. @sprites["yes"].x = 192
  235. @sprites["yes"].y = 130
  236. @sprites["no"] = Sprite.new(@viewport)
  237. @sprites["no"].bitmap = Bitmap.new("Graphics/UI/Boolean/no")
  238. @sprites["no"].x = 192
  239. @sprites["no"].y = 210
  240. @sprites["no"].opacity = 150
  241. @sprites["sel"] = Sprite.new(@viewport)
  242. @sprites["sel"].bitmap = Bitmap.new("Graphics/UI/Boolean/cursor")
  243. @sprites["sel"].x = 192
  244. @sprites["sel"].y = 130
  245. main
  246. end
  247.  
  248. def main
  249. loop do
  250. Graphics.update
  251. Input.update
  252. if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN)
  253. pbPlayCursorSE
  254. @sel = (@sel+1)%2
  255. if @sel == 0
  256. @sprites["yes"].opacity = 255
  257. @sprites["no"].opacity = 150
  258. 16.times do
  259. @sprites["sel"].y -= 5
  260. Graphics.update
  261. end
  262. else
  263. @sprites["yes"].opacity = 150
  264. @sprites["no"].opacity = 255
  265. 16.times do
  266. @sprites["sel"].y += 5
  267. Graphics.update
  268. end
  269. end
  270. end
  271. break if Input.trigger?(Input::BACK)
  272. #if Input.trigger?(Input::C)
  273. # cmd = pbConfirmMessage(_INTL("Are you sure you're a #{@sel == 0 ? "boy" : "girl"}?"))
  274. # if cmd
  275. # pbChangePlayer(@sel)
  276. # break
  277. # end
  278. #end
  279. end
  280. dispose
  281. end
  282.  
  283. def dispose
  284. pbDisposeSpriteHash(@sprites)
  285. @viewport.dispose
  286. end
  287. end
  288. [/code]
  289. ((v3a video)
  290.  
  291. You can see from the video that this doesn't really seem very different in-game. The only difference from a player's perspective is that the animation played out in full before control was returned to the player, while in my original version, I could switch back while the cursor was still moving. It's a pretty subtle difference, especially with an animation this fast, but it is something worth considering depending on what you're using your UI for.
  292.  
  293. Everything else only matters from the dev's perspective, but I find these changes to be pretty handy all the same!
  294. -There's less redundant code. This doesn't matter a whole lot (especially since, with the additions in the goal version, this one is only one line longer), but I personally like to reduce redundancies as much as possible.
  295. -In the loop version, my end result isn't written anywhere, I have to do math to figure out the final value. (starting value + (times looped * value added)) In the goal version, I've set the end value directly, and use that as the base for my math instead.
  296. --For example, say I want to space out the buttons a little more, and I want the cursor's y-value at 230 instead of 210. In my goal script, I'd just change this line -
  297. @selgoal = 130 + (80 * @sel)
  298. to
  299. @selgoal = 130 + (100 * @sel)
  300. But in my loop script, I'd have to do a quick bit of math - 130 + 5x = 230 - and then change the number of loops both times it's called.
  301. 20.times do
  302. @sprites["sel"].y -= 5
  303. Graphics.update
  304. end
  305. 20.times do
  306. @sprites["sel"].y += 5
  307. Graphics.update
  308. end
  309. It's not hard work in the slightest, but it's just a little more tedious IMO, especially when you're playtesting different values to figure out how you want your UI.
  310.  
  311. Similarly - since I don't have a set number of loops, I can change the speed more easily. If I want to try doubling the speed - moving 10 pixels per frame instead of 5 - then on the loop version, I have to change both the number of loops and the number of pixels, both times they're called.
  312. 8.times do
  313. @sprites["sel"].y += 10
  314. Graphics.update
  315. end
  316. 8.times do
  317. @sprites["sel"].y -= 10
  318. Graphics.update
  319. end
  320. But on the goal version, I only have to change one line.
  321. dif = 10
  322. Again, the extra steps in the loop version aren't hard by any means, but if you can cut them out and save time, why not?
  323.  
  324. Anyways! Now that I've made my pitch, let me outline the actual process behind my goal method:
  325. -In initialize, set an instance variable @goal with the default value for the attribute in question
  326. -In the main loop:
  327. --Create an variable dif that is an integer with a common factor between the different goal values. In other words, whatever goal values you have should all be divisible by dif.
  328. --Check if the attribute is greater than dif. If it is, multiply dif by -1. (This makes sure the value is decreasing if the goal is below it, and increasing if the goal is above it.)
  329. --Add dif to the attribute if the attribute is not equal to the goal value
  330. -
  331.  
  332.  
  333. So, in my code, I'm applying this method with the y-value of the sel sprite, @sprites["sel"].y. It starts at 130, and moves between 130 and 230.
  334. -In initialize, I set the instance variable @selgoal at 130.
  335. -In the main loop:
  336. --I set the variable dif to 5, because both 130 and 230 can be divided by 5.
  337. --I check if @sprites["sel"].y is greater than @selgoal, and if it is, multiple dif by -1.
  338. --I check if @sprites["sel"].y is equal to @selgoal. If it's not, I add dif to @sprites["sel"].y.
  339. --When the player selects "No", I change @selgoal to 230, and when they select "Yes", I change @selgoal back to 130.
  340.  
  341. It's important that dif is a common factor of your goal values, because if it's not, then your attribute will never land on your goal value, and the check to see if it's reached will always fail. For example, if I set dif to 60 here, it would go 130, 190, 250, and completely skip over 230. Now it's over 230, so it'll try going down - but it'll go down by 60, so it's back to 190, and it's skipped 230 again.
  342.  
  343. There are ways around this - you could make your check if the attribute is equal to or less than/greater than the goal, but now you've got to check which direction you intended to go in, and for that much work, you might as well just adjust your goal values or switch to the loop method. You could also have a check for if dif is greater than the actual difference between the two values, and if it is, just set it to the actual difference. (Something like dif = (@goal - value).abs if dif > (@goal - value).abs ) That means it would be moving a bit faster at the end, but that might not be noticeable.
  344.  
  345. Hopefully this all makes sense - I'm trying to strike the right balance between clearly labelling things and not bogging things down with math/scripting terms, haha.
  346.  
  347. v4 - Looping Background Planes
  348. We've played with the buttons and the cursor, now how about the background?
  349.  
  350. Gen 5 included a lot of neat looping BGs
  351. [CODE lang="ruby" highlight="6, 27"]
  352. class BooleanUI
  353. def initialize
  354. @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
  355. @sprites = {}
  356. @sel = 0
  357. addBackgroundPlane(@sprites, "bg", "Boolean/bg", @viewport)
  358. @sprites["yes"] = Sprite.new(@viewport)
  359. @sprites["yes"].bitmap = Bitmap.new("Graphics/UI/Boolean/yes")
  360. @sprites["yes"].x = 192
  361. @sprites["yes"].y = 130
  362. @sprites["no"] = Sprite.new(@viewport)
  363. @sprites["no"].bitmap = Bitmap.new("Graphics/UI/Boolean/no")
  364. @sprites["no"].x = 192
  365. @sprites["no"].y = 210
  366. @sprites["no"].opacity = 150
  367. @sprites["sel"] = Sprite.new(@viewport)
  368. @sprites["sel"].bitmap = Bitmap.new("Graphics/UI/Boolean/cursor")
  369. @sprites["sel"].x = 192
  370. @sprites["sel"].y = 130
  371. @selgoal = 130
  372. main
  373. end
  374.  
  375. def main
  376. loop do
  377. Graphics.update
  378. Input.update
  379. @sprites["bg"].ox-= -1
  380. if @sprites["sel"].y != @selgoal
  381. dif = 5
  382. dif *= -1 if @sprites["sel"].y > @selgoal
  383. @sprites["sel"].y += dif
  384. end
  385. if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN)
  386. pbPlayCursorSE
  387. @sel = (@sel+1)%2
  388. @selgoal = 130 + (80 * @sel)
  389. if @sel == 0
  390. @sprites["yes"].opacity = 255
  391. @sprites["no"].opacity = 150
  392. else
  393. @sprites["yes"].opacity = 150
  394. @sprites["no"].opacity = 255
  395. end
  396. end
  397. break if Input.trigger?(Input::BACK)
  398. #if Input.trigger?(Input::C)
  399. # cmd = pbConfirmMessage(_INTL("Are you sure you're a #{@sel == 0 ? "boy" : "girl"}?"))
  400. # if cmd
  401. # pbChangePlayer(@sel)
  402. # break
  403. # end
  404. #end
  405. end
  406. dispose
  407. end
  408.  
  409. def dispose
  410. pbDisposeSpriteHash(@sprites)
  411. @viewport.dispose
  412. end
  413. end
  414. [/code]
  415. Be sure to explain formatting, how the movement works - x-1 is the looping in the video, how to slow down
  416. COuld do GIF but programming it is easier to tweak
  417. Mention the arguments pbAddBackgroundPLane takes
  418.  
  419. v5 - change cursor to animated arrow
  420. include -
  421. -Arguments taken for animated sprite
  422. -How I got the new coordinates
  423. -The .update in the loop
  424. [CODE lang="ruby" highlight="16-20, 29, 38"]
  425. class BooleanUI
  426. def initialize
  427. @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
  428. @sprites = {}
  429. @sel = 0
  430. addBackgroundPlane(@sprites, "bg", "Boolean/bg", @viewport)
  431. @sprites["yes"] = Sprite.new(@viewport)
  432. @sprites["yes"].bitmap = Bitmap.new("Graphics/UI/Boolean/yes")
  433. @sprites["yes"].x = 192
  434. @sprites["yes"].y = 130
  435. @sprites["no"] = Sprite.new(@viewport)
  436. @sprites["no"].bitmap = Bitmap.new("Graphics/UI/Boolean/no")
  437. @sprites["no"].x = 192
  438. @sprites["no"].y = 210
  439. @sprites["no"].opacity = 150
  440. @sprites["sel"] = AnimatedSprite.new("Graphics/UI/right_arrow", 8, 40, 28, 2, @viewport)
  441. @sprites["sel"].x = 156
  442. @sprites["sel"].y = 150
  443. @sprites["sel"].play
  444. @selgoal = 150
  445. main
  446. end
  447.  
  448. def main
  449. loop do
  450. Graphics.update
  451. Input.update
  452. @sprites["bg"].ox-= -1
  453. @sprites["sel"].update
  454. if @sprites["sel"].y != @selgoal
  455. dif = 5
  456. dif *= -1 if @sprites["sel"].y > @selgoal
  457. @sprites["sel"].y += dif
  458. end
  459. if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN)
  460. pbPlayCursorSE
  461. @sel = (@sel+1)%2
  462. @selgoal = 150 + (80 * @sel)
  463. if @sel == 0
  464. @sprites["yes"].opacity = 255
  465. @sprites["no"].opacity = 150
  466. else
  467. @sprites["yes"].opacity = 150
  468. @sprites["no"].opacity = 255
  469. end
  470. end
  471. break if Input.trigger?(Input::BACK)
  472. #if Input.trigger?(Input::C)
  473. # cmd = pbConfirmMessage(_INTL("Are you sure you're a #{@sel == 0 ? "boy" : "girl"}?"))
  474. # if cmd
  475. # pbChangePlayer(@sel)
  476. # break
  477. # end
  478. #end
  479. end
  480. dispose
  481. end
  482.  
  483. def dispose
  484. pbDisposeSpriteHash(@sprites)
  485. @viewport.dispose
  486. end
  487. end
  488. [/code]
  489.  
  490. v6 - adding mouse function
  491. Mention -
  492. -arguments that rectangles take
  493. -Might want to check bigger area
  494. -Remember to allow for possibility mouse isn't there
  495. -Remember to consider when mouse is over button that's already selected
  496.  
  497. [CODE lang="ruby" highlight="11, 17, 26-40, 46, 55-65"]
  498. class BooleanUI
  499. def initialize
  500. @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
  501. @sprites = {}
  502. @sel = 0
  503. addBackgroundPlane(@sprites, "bg", "Boolean/bg", @viewport)
  504. @sprites["yes"] = Sprite.new(@viewport)
  505. @sprites["yes"].bitmap = Bitmap.new("Graphics/UI/Boolean/yes")
  506. @sprites["yes"].x = 192
  507. @sprites["yes"].y = 130
  508. @yesrect = Rect.new(@sprites["yes"].x,@sprites["yes"].y,@sprites["yes"].width,@sprites["yes"].height)
  509. @sprites["no"] = Sprite.new(@viewport)
  510. @sprites["no"].bitmap = Bitmap.new("Graphics/UI/Boolean/no")
  511. @sprites["no"].x = 192
  512. @sprites["no"].y = 210
  513. @sprites["no"].opacity = 150
  514. @norect = Rect.new(@sprites["no"].x,@sprites["no"].y,@sprites["no"].width,@sprites["no"].height)
  515. @sprites["sel"] = AnimatedSprite.new("Graphics/UI/right_arrow", 8, 40, 28, 2, @viewport)
  516. @sprites["sel"].x = 156
  517. @sprites["sel"].y = 150
  518. @sprites["sel"].play
  519. @selgoal = 150
  520. main
  521. end
  522.  
  523. def sel_yes
  524. pbPlayCursorSE
  525. @sprites["yes"].opacity = 255
  526. @sprites["no"].opacity = 150
  527. @sel = 0
  528. @selgoal = 150
  529. end
  530.  
  531. def sel_no
  532. pbPlayCursorSE
  533. @sprites["yes"].opacity = 150
  534. @sprites["no"].opacity = 255
  535. @sel = 1
  536. @selgoal = 230
  537. end
  538.  
  539. def main
  540. loop do
  541. Graphics.update
  542. Input.update
  543. mousepos = Mouse.getMousePos
  544. @sprites["bg"].ox-= -1
  545. @sprites["sel"].update
  546. if @sprites["sel"].y != @selgoal
  547. dif = 5
  548. dif *= -1 if @sprites["sel"].y > @selgoal
  549. @sprites["sel"].y += dif
  550. end
  551. if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN)
  552. if @sel == 0
  553. sel_no
  554. else
  555. sel_yes
  556. end
  557. end
  558. if mousepos
  559. if @yesrect.contains?(mousepos[0], mousepos[1]) && @sel == 1
  560. sel_yes
  561. elsif @norect.contains?(mousepos[0], mousepos[1]) && @sel == 0
  562. sel_no
  563. end
  564. end
  565. break if Input.trigger?(Input::BACK)
  566. #if Input.trigger?(Input::C)
  567. # cmd = pbConfirmMessage(_INTL("Are you sure you're a #{@sel == 0 ? "boy" : "girl"}?"))
  568. # if cmd
  569. # pbChangePlayer(@sel)
  570. # break
  571. # end
  572. #end
  573. end
  574. dispose
  575. end
  576.  
  577. def dispose
  578. pbDisposeSpriteHash(@sprites)
  579. @viewport.dispose
  580. end
  581. end
  582. [/code]
  583.  
  584. v7 - Adding animation by changing bitmap
  585.  
  586.  
  587. We're finally uncommenting that code!
  588.  
  589.  
  590. [CODE lang="ruby" highlight="69-92"]
  591. class BooleanUI
  592. def initialize
  593. @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
  594. @sprites = {}
  595. @sel = 0
  596. addBackgroundPlane(@sprites, "bg", "Boolean/bg", @viewport)
  597. @sprites["yes"] = Sprite.new(@viewport)
  598. @sprites["yes"].bitmap = Bitmap.new("Graphics/UI/Boolean/yes")
  599. @sprites["yes"].x = 192
  600. @sprites["yes"].y = 130
  601. @yesrect = Rect.new(@sprites["yes"].x,@sprites["yes"].y,@sprites["yes"].width,@sprites["yes"].height)
  602. @sprites["no"] = Sprite.new(@viewport)
  603. @sprites["no"].bitmap = Bitmap.new("Graphics/UI/Boolean/no")
  604. @sprites["no"].x = 192
  605. @sprites["no"].y = 210
  606. @sprites["no"].opacity = 150
  607. @norect = Rect.new(@sprites["no"].x,@sprites["no"].y,@sprites["no"].width,@sprites["no"].height)
  608. @sprites["sel"] = AnimatedSprite.new("Graphics/UI/right_arrow", 8, 40, 28, 2, @viewport)
  609. @sprites["sel"].x = 156
  610. @sprites["sel"].y = 150
  611. @sprites["sel"].play
  612. @selgoal = 150
  613. main
  614. end
  615.  
  616. def sel_yes
  617. pbPlayCursorSE
  618. @sprites["yes"].opacity = 255
  619. @sprites["no"].opacity = 150
  620. @sel = 0
  621. @selgoal = 150
  622. end
  623.  
  624. def sel_no
  625. pbPlayCursorSE
  626. @sprites["yes"].opacity = 150
  627. @sprites["no"].opacity = 255
  628. @sel = 1
  629. @selgoal = 230
  630. end
  631.  
  632. def main
  633. loop do
  634. Graphics.update
  635. Input.update
  636. mousepos = Mouse.getMousePos
  637. @sprites["bg"].ox-= -1
  638. @sprites["sel"].update
  639. if @sprites["sel"].y != @selgoal
  640. dif = 5
  641. dif *= -1 if @sprites["sel"].y > @selgoal
  642. @sprites["sel"].y += dif
  643. end
  644. if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN)
  645. if @sel == 0
  646. sel_no
  647. else
  648. sel_yes
  649. end
  650. end
  651. if mousepos
  652. if @yesrect.contains?(mousepos[0], mousepos[1]) && @sel == 1
  653. sel_yes
  654. elsif @norect.contains?(mousepos[0], mousepos[1]) && @sel == 0
  655. sel_no
  656. end
  657. end
  658. break if Input.trigger?(Input::BACK)
  659. if Input.trigger?(Input::C) || Input.trigger?(Input::MOUSELEFT)
  660. Input.update
  661. pbPlayDecisionSE
  662. button = (@sel == 0) ? "yes" : "no"
  663. @sprites["sel"].visible = false
  664. Graphics.update
  665. @sprites["#{button}"].bitmap = Bitmap.new("Graphics/UI/Boolean/#{button}1")
  666. 12.times do
  667. Graphics.update
  668. @sprites["bg"].ox-= -1
  669. end
  670. @sprites["#{button}"].bitmap = Bitmap.new("Graphics/UI/Boolean/#{button}2")
  671. 8.times do
  672. Graphics.update
  673. @sprites["bg"].ox-= -1
  674. end
  675. @sprites["#{button}"].bitmap = Bitmap.new("Graphics/UI/Boolean/#{button}3")
  676. 30.times do
  677. Graphics.update
  678. @sprites["bg"].ox-= -1
  679. end
  680. break
  681. end
  682. end
  683. dispose
  684. end
  685.  
  686. def dispose
  687. pbDisposeSpriteHash(@sprites)
  688. @viewport.dispose
  689. end
  690. end
  691. [/code]
  692.  
  693. Here, I've got it just directly changing the bitmap of the sprite by loading a new one. This is okay on its own - you can see in the video that it runs just fine - but it does mean I have four image files for each button, and the game is loading a new bitmap three times in the animation. Let's see if we can simplify that a bit by making these buttons one sprite sheet that the game moves through!
  694.  
  695. We're going to be making use of the src_rect property of a sprite! A sprite's src_rect (short for "source rectangle") is the portion it takes from its base bitmap. This is usually the whole image, of course, but when we want to select just part of it, we'll start manipulating the src_rect command! (This is actually how the standard spritesheets in the overworld work!)
  696.  
  697. sprite.src_rect.set(x,y,width,height) will create a new src_rect for the sprite in question, starting at the point (x,y), and selecting the width left to right and the height top to bottom. (Sprites naturally have src_rects by default, starting at 0,0 and using the full width and height of the base bitmap, but you usually won't be manipulating a src_rect like that)
  698.  
  699. Since this is original code, I can choose to format my spritesheet however I want it! I'm going to keep it simple and just set each frame directly below each other, like this.
  700.  
  701. I'm still starting at 0,0, and I'm using the full width of my image, so I could actually just edit the height of the src_rect directly...
  702.  
  703. @sprites["yes"].src_rect.height = 60
  704.  
  705. But just for clarity's sake, I'll use the src_rect.set command.
  706.  
  707.  
  708. @sprites["yes"].src_rect.set(0,0,96,60)
  709.  
  710. Now, instead of generating a new bitmap, I'll just change the y-coordinate of the src_rect! This coordinate is where the src_rect is on the graphic, not where the image is displayed on my viewport, so it's just making it select from a different portion!
  711.  
  712. [CODE lang="ruby" highlight="4, 9, 14"]
  713. button = (@sel == 0) ? "yes" : "no"
  714. @sprites["sel"].visible = false
  715. Graphics.update
  716. @sprites["#{button}"].src_rect.y += 60
  717. 12.times do
  718. Graphics.update
  719. @sprites["bg"].ox-= -1
  720. end
  721. @sprites["#{button}"].src_rect.y += 60
  722. 8.times do
  723. Graphics.update
  724. @sprites["bg"].ox-= -1
  725. end
  726. @sprites["#{button}"].src_rect.y += 60
  727. 60.times do
  728. Graphics.update
  729. @sprites["bg"].ox-= -1
  730. end
  731. [/code]
  732.  
  733. And there we go! It works just as well as the previous code, and now my sprites are a bit more organized, too!
  734.  
  735. ((insert - v7a))
  736.  
  737. [code=RUBY]
  738. class BooleanUI
  739. def initialize
  740. @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
  741. @sprites = {}
  742. @sel = 0
  743. addBackgroundPlane(@sprites, "bg", "Boolean/bg", @viewport)
  744. @sprites["yes"] = Sprite.new(@viewport)
  745. @sprites["yes"].bitmap = Bitmap.new("Graphics/UI/Boolean/button_yes")
  746. @sprites["yes"].src_rect.set(0,0,96,60)
  747. @sprites["yes"].x = 192
  748. @sprites["yes"].y = 130
  749. @yesrect = Rect.new(@sprites["yes"].x,@sprites["yes"].y,@sprites["yes"].width,@sprites["yes"].height)
  750. @sprites["no"] = Sprite.new(@viewport)
  751. @sprites["no"].bitmap = Bitmap.new("Graphics/UI/Boolean/button_no")
  752. @sprites["no"].src_rect.set(0,0,96,60)
  753. @sprites["no"].x = 192
  754. @sprites["no"].y = 210
  755. @sprites["no"].opacity = 150
  756. @norect = Rect.new(@sprites["no"].x,@sprites["no"].y,@sprites["no"].width,@sprites["no"].height)
  757. @sprites["sel"] = AnimatedSprite.new("Graphics/UI/right_arrow", 8, 40, 28, 2, @viewport)
  758. @sprites["sel"].x = 156
  759. @sprites["sel"].y = 150
  760. @sprites["sel"].play
  761. @selgoal = 150
  762. main
  763. end
  764.  
  765. def sel_yes
  766. pbPlayCursorSE
  767. @sprites["yes"].opacity = 255
  768. @sprites["no"].opacity = 150
  769. @sel = 0
  770. @selgoal = 150
  771. end
  772.  
  773. def sel_no
  774. pbPlayCursorSE
  775. @sprites["yes"].opacity = 150
  776. @sprites["no"].opacity = 255
  777. @sel = 1
  778. @selgoal = 230
  779. end
  780.  
  781. def main
  782. loop do
  783. Graphics.update
  784. Input.update
  785. mousepos = Mouse.getMousePos
  786. @sprites["bg"].ox-= -1
  787. @sprites["sel"].update
  788. if @sprites["sel"].y != @selgoal
  789. dif = 5
  790. dif *= -1 if @sprites["sel"].y > @selgoal
  791. @sprites["sel"].y += dif
  792. end
  793. if Input.trigger?(Input::UP) || Input.trigger?(Input::DOWN)
  794. if @sel == 0
  795. sel_no
  796. else
  797. sel_yes
  798. end
  799. end
  800. if mousepos
  801. if @yesrect.contains?(mousepos[0], mousepos[1]) && @sel == 1
  802. sel_yes
  803. elsif @norect.contains?(mousepos[0], mousepos[1]) && @sel == 0
  804. sel_no
  805. end
  806. end
  807. break if Input.trigger?(Input::BACK)
  808. if Input.trigger?(Input::C) || Input.trigger?(Input::MOUSELEFT)
  809. Input.update
  810. pbPlayDecisionSE
  811. button = (@sel == 0) ? "yes" : "no"
  812. @sprites["sel"].visible = false
  813. Graphics.update
  814. @sprites["#{button}"].src_rect.y += 60
  815. 12.times do
  816. Graphics.update
  817. @sprites["bg"].ox-= -1
  818. end
  819. @sprites["#{button}"].src_rect.y += 60
  820. 8.times do
  821. Graphics.update
  822. @sprites["bg"].ox-= -1
  823. end
  824. @sprites["#{button}"].src_rect.y += 60
  825. 60.times do
  826. Graphics.update
  827. @sprites["bg"].ox-= -1
  828. end
  829. break
  830. end
  831. end
  832. dispose
  833. end
  834.  
  835. def dispose
  836. pbDisposeSpriteHash(@sprites)
  837. @viewport.dispose
  838. end
  839. end
  840. [/code]
  841.  
  842. Conclusion
  843.  
  844. So we've gone from this...
  845. ((insert v1
  846. to this!
  847. ((insert v7a
  848.  
  849. It was fair amount of code - it went from 41 lines to 102 lines - but nothing too complicated!
  850.  
  851. I hope seeing these two side-by-side helps drive the point a little more - these are all pretty simple changes, but they really make things look a lot nicer!
  852.  
  853. This script (in all its forms) is free to use as long as you credit me, TechSkylander1518. The graphics can be downloaded here. ((insert link) (I have to warn you - this doesn't actually return true or false in its current state! IMO the simplest thing to do would be to make it set a switch, and use that for whatever conditional branch you want)
  854.  
  855. As one last bit of showing off... I thought it'd be fun to adapt Aki's [URL='http://Ribbons Style summary']Ribbons Style summary[/URL] to animate the ribbons! (and figured I'd add wraparound while I'm at it)
  856. ((insert akidemo
  857. The edited assets for this can be found ((insert link here.
  858. I'm not making this plug-and-play, because it's a lot simpler to make the edits than it is to rewrite several methods in the summary UI. The changes are as follows:
  859. [spoiler]
  860. All of these changes are in UI_Summary.
  861.  
  862. 1 -
  863. Change this bit of code
  864. def pbUpdate
  865. pbUpdateSpriteHash(@sprites)
  866. end
  867. to
  868. def pbUpdate
  869. pbUpdateSpriteHash(@sprites)
  870. for i in 1..5
  871. if i != @selrib
  872. @sprites["ribbon#{i}"].y -= 2 if @sprites["ribbon#{i}"].y > -44
  873. else
  874. @sprites["ribbon#{i}"].y += 2 if @sprites["ribbon#{i}"].y < 0
  875. end
  876. end
  877. end
  878.  
  879. 2 - Under this line
  880. @sprites["markingsel"].visible = false
  881. Add
  882. @selrib = 1
  883. for i in 1..5
  884. @sprites["ribbon#{i}"] = Sprite.new(@viewport)
  885. @sprites["ribbon#{i}"].bitmap = Bitmap.new("Graphics/UI/Summary/ribbon#{i}")
  886. @sprites["ribbon#{i}"].x = 230 + (56 * (i-1))
  887. @sprites["ribbon#{i}"].y = (i == @selrib) ? 0 : -44
  888. end
  889.  
  890. 3 - With each method
  891. def pbDrawPageOne
  892. @selrib = 1
  893.  
  894. def pbDrawPageTwo
  895. @selrib = 2
  896.  
  897. And so on.
  898.  
  899. 4 - Wraparound -
  900. Both instances of these lines
  901. @page = 1 if @page < 1
  902. @page = 5 if @page > 5
  903. Change them to -
  904. @page = 5 if @page < 1
  905. @page = 1 if @page > 5
  906. [/spoiler]
  907.  
  908. Please remember to credit both Akizakura16 and me if you use this!
Add Comment
Please, Sign In to add comment