kvagram

litiengine extra camera dev notes

Mar 23rd, 2020
103
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.34 KB | None | 0 0
  1. I have taken up the task of adding support for "more than one camera" in litiengine.
  2.  
  3. Now, there are few details that come with this feature, so I have to make assumtions.
  4. So, I make two major assumtions of use-cases here.
  5.  
  6. 1: user wants to hot-switch between two diffrent cameras on the fly.
  7. 2: The user want to watch two or more different perspectives at the same time on the same screen.
  8. 2b: possebly in some complex ui layout.
  9.  
  10. My goal would be to create a feature that would cover both usecases
  11.  
  12. Some notes on existing structure regarding the camera:
  13. 1: Camera is data and calculation only, it does not render anything.
  14. 2: The Camera class is not a singleton (it could easely have been one, luckly it is not)
  15. 3: The camera class is only ever created once as a part of the GameWorld class, which is a singleton.
  16. 4: The private value of GameWorld#camera is never used directly outside a standard setter / getter pattern.
  17. 5: The setter is only used indirectly via GameWorld#setCamera
  18. 6: While setCamera is only used once by the engine itself, it is left exposed to use by game projects, presumably as standard API.
  19.  
  20. So, this creates a starting point.
  21. I need to add support for more cameras to GameWorld.
  22.  
  23. Current idea on how to do this:
  24. * Add a new integer property GameWorld#currentCamera. This is used when rendering to keep track of the index what camera is in use.
  25. * Change the private variable GameWorld#camera from a single iCamera value to a iCamera array.
  26. * This invalidates the setter and getter. fix: add new setter and getter with index paramater, have the old setter and getter redirect with index GameWorld#currentCamera
  27. * Problem: GameWorld#camera still must be initalized, but it is now an array.
  28. * Possible fixes:
  29. * Use a dynamic list (may be costly)
  30. * Use an array utility tool to rebuild a new array everytime a camera is added or removed. (may be a bit of a kludge)
  31. * Have number of cameras set in game settings, and default to 2.
  32. * In any case, at least one camera must be created and initiated.
  33. * Problem: The feature issue mention the need to clip the camera render.
  34. * This is presumably a part of usecase #2.
  35. * Fix: when starting render of viewport of camera, create a child graphics object with a restricted cliprect. This is a native feature we should be able to trust.
  36.  
  37.  
  38. Before I can start doing anything, I need a plan, and I need to understand the render-flow of the engine
  39.  
  40. * The GameWindow class is an indirect singleton via the Game class. There is no need to change this (with the possible exception of merging GameWindow into Game)
  41. * GameWindow contains two properties of interest. The renderCanvas (RenderComponent) and hostControl (JFrame). All rendering seems to be done on renderCanvas.
  42. * note: there is a naming inconsistency here. The getter for GameWindow#renderCanvas is named GameWindow#GetRenderComponent, and is of class RenderComponent.
  43. * The Graphics instance used to render EVERYTHING seems to be created in RenderComponent#Render via the RenderComponent#currentBufferStrategy property.
  44. * I super-duper do not want to fuck with this.
  45. * BufferStrategy is part of the AWT package, so I suppose I can confirm I have found the root of the Graphics object for the engine.
  46. * The RenderComponent#render function does wisely set the cliprect of the master Graphics object.
  47. * There are never created any child graphics objects of the master Graphics Object.
  48. * This is a HUGE problem, as any render function could mess up everything called afterwards.
  49. * The master Graphics Object is cliped in GuiComponent#render, only to be restored shortly afterwards.
  50. * The render flow is as such
  51. 1: GameLoop calls RenderComponent#render
  52. 2: render updates fade values
  53. 3: render gets a new Graphics2D from the buffer system (presumably it is created in that system somewhere somehow, not important)
  54. 4: render fills with background color and sets AA and interpolation settings
  55. 5: render gets the current Screen object from Game.
  56. 6: render calls render on the current screen.
  57. 7: render calls render on the cursor object
  58. 8: ?? render runns accept on a list of renderedComsumer
  59. I need to figure out what this is used for.
  60. This looks like an Event pattern.
  61. 9: render applies fade effect if any.
  62. 10:render takes and saves screenshot, if requested.
  63. NOTE: this is done by rendering current screen all over again !!
  64. NOTE: fade effet would not be applied on screenshot.
  65.  
  66. * Based on this, I can form a plan for implementing mulible camera support.
  67. * First of, add the extra camera support as theorized earlier.
  68. * NOTE: first idea for this next part was based on Screen overwriteing render from GuiComponent, but this would be void as GameScreen runs it's render before calling super.
  69. * So a change has to happen in RenderComponent.
  70. * In RenderComponent#render, add the following after `if (currentScreen != null) {`
  71. 1: create a child instance of argument Graphics2D g, let's call it screenG.
  72. 2: apply any required local translations of the Screen object (x, y, clipRect, rotation, scale, etc.) to screenG.
  73. 3: change `currentScreen.render(g);` to `currentScreen.render(screenG);`
  74. * Now in Screen class:
  75. * Remove the setX and setY overrides, as the given statement in those comments are now incorrect.
  76.  
  77. * What does this give us?
  78. * A screen may now exist on any arbitrary place within the rendering canvas, and should, if I understand awt correctly, clip accourding to the cliprect.
  79. * We are limited to just one screen object, and the game only uses one camera.
  80. * To implement a use of extra cameras, we add a feature to GameScreen.
  81. * Note: we do not need to touch the Screen class for this, as it does not use a camera.
  82.  
  83. * In the GameScreen class:
  84. * add this parameter: `int cameraIndex`.
  85. * add a setter for cameraIndex that checks if the index is legal compared to the GameWorld settings (sets to 0 if it fails)
  86. * in the existing constructors, initiate cameraIndex with the value 0.
  87. * Add a new constructor that takes in the parameters `(String name, int cameraIndex)`, have it set cameraIndex using the setter (as in, do not trust the input, aka assume the user (game developer) is an idiot, as we often are)
  88.  
  89. * With that, we now have cover for usercase #1, and laid the groundwork for usercase #2 and #2b.
  90. * Next fix is: support for multible screens instances!
  91. * Again, this will take some changes, as like the camera, the game assumes there can only be one Screen object active at the time.
  92. * First, in Screen class:
  93. * Add a new int property Screen#screenLayer
  94. * in the existing constructor, initialize screenLayer as 0
  95. * Create a new constructor with a parameter for screenLayer.
  96. * In GameScreen:
  97. * Add a complementary constructor that passes a ScreenLayer to its superclass Screen
  98. * This one is worse, however, as the ScreenManager includes properties that relies on this assumption. Care must be made to prevent feature-braking.
  99. * First of, replace ScreenManager#currentScreen property with a new ScreenManager#activeScreens property of type dynamic list of Screen.
  100. * Add a new getter ScreenManager#ActiveScreens for ScreenManager#activeScreens.
  101. * Mark the getter ScreenManager#CurrentScreen as obsolete (or depreacted), and have it return the first item in ScreenManager#ActiveScreens as a fallback.
  102.  
  103. * Add a new function ScreenManager#RemoveScreen with parameter oldScreen
  104. * It should do this:
  105. 1: return if oldScreen is null or not found in activeScreens
  106. 2: call suspend on oldScreen
  107. * To keep with the code pattern, add a string overload oldScreenName
  108. * Find oldScreen by name from ActiveScreens, remove if found.
  109. * As replacements ScreenManager#Display, add a new function ScreenManager#ReplaceDisplay that takes two arguments: Screen oldScreen and Screen newScreen.
  110. * To keep with the existing pattern, add an overload with arguments: String oldScreenName and String newScreenName.
  111. * Get a ScreenObject from ScreenManager#ActiveScreens by searching for oldScreenName. This will be used as argument Screen oldScreen
  112. * Reuse code from ScreenManager#Display(String) to get Screen newScreen from newScreenName.
  113. * Call ScreenManager#ReplaceDisplay(Screen, Screen)
  114. * Note: null-values should be logged, but will be handled.
  115. * This new function will have this flow:
  116. 1: Call ScreenManager#RemoveScreen on oldScreen.
  117. * null value will be handled, and is safe
  118. 2: return if newScreen is null.
  119. 3: return if newScreen already exist in activeScreens
  120. 4: Add newScreen to activeScreens
  121. 5: sort activeScreens by Screen#screenLayer
  122. 6: call prepeare on NewScreen
  123. * Add a new function ScreenManager#AddDisplay, with a Screen argument and an overload with a string argument. Have them both redirect to their equivalent ScreenManager#ReplaceDisplay with null as argument for oldScreen.
  124. * Since there are null-handlers in ReplaceDisplay, this is fine, we don't need to duplicate code
  125. * Mark both overloads of ScreenManager#Display as obsolete/depracted, have them redirect to ScreenManager#ReplaceDisplay with first item of ScreenManager#activeScreens as the argument for oldScreen
  126.  
  127. * Last thing to do, is to change up RenderComponent#render once again.
  128. * remove the line ´final Screen currentScreen = Game.screens().current();´
  129. * Replace ´if (currentScreen != null) {´ with ´for each ( Screen currentScreen : Game.screens().ActiveScreens()´
  130. * Find the line for rendering screenshots ´currentScreen.render(imgGraphics);´, wrap it with the same for each loop as above.
  131.  
  132. * Note: no replacements found so far for the cooldown properties. implementing them in the same way may break intended use. However, since repeated calls for adding the same screen would be stopped before reaching the prepeare call, we should be good.
  133. * Note: further enhancements could be made by adding functions that sets the translations of all active screens into a desired layout, for example side-by-side for use in co-op.
  134. * Note: Since RenderEngine#renderEnteties has been marked as a costly function, I suggest an optimization of filtering for all screens and storing the list as a first step in the render flow, that way we can save on some prosessing power when adding more than one GameScreen.
  135.  
  136.  
  137. And now, dear reader of my notes, know that at time of writing these 137 lines of notes, not a single line of real code has been written so far.
  138. Implementation begins soon. And I hope it goes according to plan.
Add Comment
Please, Sign In to add comment