-
Notifications
You must be signed in to change notification settings - Fork 470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds support for per scene and project wide DMG palettes #1601
base: develop
Are you sure you want to change the base?
Adds support for per scene and project wide DMG palettes #1601
Conversation
…lement and what the fuck is this language
… a component out of this
…rite palettes in scene view
…ng pal fields to the scene view
…t caused values not to save
src/shared/lib/helpers/color.ts
Outdated
export const indexSpriteColour = (g: number, objPalette: ObjPalette) => { | ||
if (g < 65) { | ||
return 3; | ||
} | ||
if (g < 130) { | ||
return objPalette === "OBP1" ? 2 : 3; | ||
} | ||
if (g < 205) { | ||
return objPalette === "OBP1" ? 2 : 1; | ||
} | ||
return 0; | ||
//NOTE: this assumes GB Studio's sprite image requirements are met... https://www.gbstudio.dev/docs/assets/sprites#image-requirements | ||
export const indexSpriteColour = (g: number, objPalette: [number, number, number, number]) => { | ||
if (g < 130) return objPalette[3]; | ||
if (g < 205) return objPalette[2]; | ||
return objPalette[1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also fixes a bug. Because "white" is always used as transparency, only 3 colors can actually be mapped. The previous version of this function only returned 3 different numbers (either 0,1,3 or 0,2,3), but it did so with 4 different inputs. This resulted in editor previews of sprites set to OBP1 that were different than what you would see in builds. Of course, you could only see this preview in the sprite sheet select in the game world view, so I don't suppose most folks would have noticed.
const value = useAppSelector((state) => { | ||
if (state.project.present.settings.previewAsMono) return "0"; | ||
const sceneId = state.editor.previewAsSceneId; | ||
if (sceneId === "") return "1"; | ||
return sceneId; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a mono mode, similar to the one found on the world page, to the "preview as" drop down. Previously, only one non-scene option was available in this drop down, "Default Colors", which was used when previewAsSceneId
was "". To facilitate more options, I'm just using the stringified index of the non-scene values. To ensure a consistent experience when switching between pages, I set the default state to "0" when previewAsMono
and "1" when previewAsSceneId
is unset.
const onChange = (newValue: string) => { | ||
dispatch( | ||
settingsActions.editSettings({ | ||
previewAsMono: (newValue == "0"), | ||
}) | ||
); | ||
dispatch(editorActions.setPreviewAsSceneId(newValue)); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also using the "preview as" drop down selection to drive the previewAsMono
editor setting so if you select it here or in the sprites page, the scenes will be in monochrome when you return to the world page.
@@ -190,7 +201,8 @@ const BackgroundPreviewSettings = ({ | |||
? l10n("FIELD_PREVIEW_AS_SCENE", { | |||
sceneName: sceneName(scene, sceneIndex), | |||
}) | |||
: l10n("FIELD_PREVIEW_AS_DEFAULT")} | |||
: ([l10n("FIELD_PREVIEW_AS_MONO"), l10n("FIELD_PREVIEW_AS_DEFAULT")][+value]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As someone who has never used TypeScript before, I found the [+value]
syntax a little strange, but it's what StackOverflow told me to do. I'm considering revisiting the weird stringified index hack when I add SGB preview support anyway, but this is a little too hacky for your tastes now, let me know.
{colorsEnabled && ( | ||
<SidebarColumn> | ||
<FormRow> | ||
<FormField | ||
name="playerSpriteSheetId" | ||
label={ | ||
<> | ||
{l10n("FIELD_SCENE_BACKGROUND_PALETTES")} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that there's a palette to edit even in monochrome mode, I moved the colorsEnabled
check after the label. I had a strong urge to update the indentation everywhere, but I left it alone to avoid a massive unreadable diff. There are a few bits that looked too odd without any indentation changes, but it's probably not a bad idea to fully correct the indentation levels for consistency after the bulk of the code review is done.
@@ -816,6 +853,25 @@ export const SceneEditor = ({ id }: SceneEditorProps) => { | |||
name="playerSpriteSheetId" | |||
label={l10n("FIELD_SCENE_SPRITE_PALETTES")} | |||
> | |||
{monoEnabled && | |||
<PaletteButtons> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps PaletteButtons
was the wrong wrapper node to use here, but I used it as a quick and dirty way of keeping the OBP0 and OBP1 palette pickers on the same row.
|
||
const settings = useAppSelector((state) => state.project.present.settings); | ||
const dmgColors = [settings.customColorsWhite, settings.customColorsLight, settings.customColorsDark, settings.customColorsBlack]; | ||
const fields = isSpritePalette ? [1, 2, 3] : [0, 1, 2, 3]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At one point, I was showing all 4 input boxes for sprites. Then, I realized the lowest two bits are never used anyway and it felt silly to show them. I didn't want to change the underlying data structure from [number, number, number, number]
to number[]
though because dealing with arrays of different sizes complicated the logic further and didn't line up as well with the byte that it represented in a build.
const width = showName ? `max-width: ${isSpritePalette ? 121 : 245}px;` : "width: 100%"; | ||
const DMGPalettePickerStyle = styled.div` | ||
display: flex; | ||
${width} | ||
box-sizing: border-box; | ||
` | ||
|
||
const DMGPaletteTextStyle = styled.div` | ||
width: 9px; | ||
color: grey; | ||
font-size: 9px; | ||
text-align: center; | ||
transform-origin:50% 50%; | ||
transform: rotate(90deg) translate(-7px,8px); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot of hacky stuff going on with the styling:
- I wanted the pickers to be full width in the settings screen, but only as wide as the color palette buttons and for OBP0 and OBP1 to be on the same row in the scene editor.
- I wanted there to be text labels in the scene editor, but I wanted them to be tiny, greyed out, unobtrusive, and vertical.
- I killed off the spacing between numerical inputs so the elements look more like they belong to each other, but it makes the red selection outline look a little funky since it expects a 5px spacing.
- In the
getRows
function below, I explicitly set the background and text colors from the default color palette. Didn't know of a cleaner way of doing that. Also, the text color seems fine, but using an offset index to lookup the palette color may be problematic in the future if the default DMG palette is ever configurable. - I also changed the min size of
NumberInput
to allow both sprite palettes to fit on a single row and only be as wide as the palette buttons below it. I didn't notice any issues, but if there's a way to override the styling of child elements instead of just changing the min-width globally, I'd rather do that.
const settings = useAppSelector((state) => state.project.present.settings); | ||
const obp0 = settings.defaultOBP0; | ||
const obp1 = settings.defaultOBP1; | ||
const dmgPal = [settings.customColorsWhite, settings.customColorsLight, settings.customColorsDark, settings.customColorsBlack]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used the custom colors from settings instead of DMG_PALETTE.colors
because I thought for sure I'd be making those configurable in this PR. I still plan on doing that, but I realized how cumbersome the task was when the various workers complained about trying to access settings off the main thread, so I abandoned ship there.
...selectProps | ||
}) => { | ||
const scenes = useAppSelector((state) => sceneSelectors.selectAll(state)); | ||
const optionalScenes = (optionalLabels || []).map((optionalLabel, i) => ({ value: i.toString(), label: optionalLabel }) ) as SceneOption[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When there was only one non-scene option, a SceneOption
was created with a value of "". To facilitate more non-scene options, I use their index as the value. It's pretty hacky, but it's simple, easy to work with, and required minimal changes.
min-width: 45px; | ||
min-width: 10px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this to allow for a smaller DMGPalettePicker, and I couldn't figure out a way to override the style. I don't think it causes problems, but I need to do some more digging/testing.
ef1aaac
to
591b584
Compare
Hi @Q-Bert-Reynolds (great username btw) just getting over a bit of illness so I've been away for a while but looks like you've been pretty busy on this. From a real quick glance through the code seems reasonable, maybe a few things I'd tidy up and appreciate all the comments you've added in the PR will definitely help me reviewing this. Just in the middle of a quite big change myself (adds a Plugin Manager + a few new plugin types) so I'll want to get that merged before reviewing/merging this but from what I see we shouldn't have any conflicts. When I get to this will let you know if I have any questions. Thanks. |
I guess this is more of a feature than a bug fix. Builds had an unexpected behavior, so I changed the editor to leverage that behavior instead of changing the engine to get rid of it.
When you play a build on a DMG, palettes are reset on scene load. You can set BGP, OBP0, and OBP1 via GBVM or engine eject, but your changes are overwritten when you load the next scene. I thought this was an odd behavior, so I went digging and found out that monochrome palette data is written per scene at build time to a fixed value, and those values can't be configured. This is especially problematic for scenes where you want the player to go behind objects, but you also want black to be the color the player is in front of. I could easily eject the engine and prevent the palette loading, but it was painful looking at inverted colors in the editor.
This PR introduces a series of quality of life improvements for the DMG and GBP. It allows you to select project wide or per scene monochrome palettes (ie. BGP, OBP0, and OBP1). Your selected monochrome palettes are reflected in the scene, background, and sprite views when in mono mode or when previewing as mono. To that end, I've also added the option to preview as mono in the background and sprite views.
Nope. It doesn't touch the engine code at all. It only added 3 optional fields to scene save data (no migration needed). Most of the editor code was left untouched. Probably the most radical changes were to ColorizedImage.
I've never used TypeScript or React or Electron. I have no idea what I'm doing. I would love to know if there are better ways of going about some of this stuff - particularly with forms and styling.
In the future, I'd like to make the default monochrome colors configurable and add Super Game Boy preview options, but I decided those were beyond the scope of this PR.
Here's some screenshots: