找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 40|回复: 0

[教程] renpy圆环选色工具插件分享

[复制链接]
发表于 昨天 20:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
本帖最后由 blurred 于 2025-11-21 20:57 编辑

前言:
1.此方法由"我"单独提出,实现,测试
2.本插件在Ren'py-8.3.7能够实现效果,如果使用,请注意引擎版本
压缩包:(一键解压食用)
色环插件-浅唱.zip (3.92 KB, 下载次数: 0)

闲言:
在一个风和日丽的下午,我看到了其他游戏的设置界面,什么?!矩形选色器!自定义颜色!如此高级的功能,我的游戏也要有!
于是...
屏幕截图 2025-11-21 200029.png
我就随随便便搓了一个
参考了 [color=inherit !important]Feniks 的代码(Feniks 开发的 Ren'Py 色彩化工具 --- Colorize Tool for Ren'Py by Feniks

说明:
1.将color_picker.rpy 拖入你的游戏项目的脚本目录下
2.使用以下语句调用:
[RenPy] 纯文本查看 复制代码
    add ColorPicker(
        width=800,
        height=800,
        xpos=0.5,
        ypos=0.5,
        color_picker_zoom=0.8,
        step=5
    )

3.其中:
width,height,color_picker_zoom为调整组件大小;
xpos,ypos为调整组件位置(注意:不要使用align等调整位置,会出现奇奇怪怪的效果);
step为精度,数值越小渲染精度越高,为了良好的体验,一般情况不建议设置小于5

主要思路:
通过分析,我们可以简单列出我们的需求:需要一个圆环选择底色,将底色传递给矩形,通过矩形进行底色的饱和度和明度选择,并且返回一个字符串(例如#41ffefff
所以我们通过renpy自带的Color渲染矩形,其目的是方便更换底色,通过shader渲染圆环,其为了节省性能并且效果也差不多
同时使用pygame模块方便获取鼠标位置和制造拖动滑块并且进行位置计算。

以下是源码:
[RenPy] 纯文本查看 复制代码
    
init python:
    import pygame
    import math
    
    renpy.register_shader("white_round", 
        variables="""
            uniform vec2 u_model_size;
            uniform float u_radius;
            uniform float u_stroke_width;
            attribute vec2 a_tex_coord;
            varying vec2 v_tex_coord;
        """,
        vertex_100="""
            v_tex_coord = a_tex_coord;
        """,
        fragment_300="""
            precision highp float;
            
            vec2 uv = v_tex_coord;
            vec2 center = vec2(0.5, 0.5);
            float dist = distance(uv, center);
            if(dist <= u_radius) {
                gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
            }
            else if(dist <= u_radius + u_stroke_width) {
                gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
            }
            else {
                gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
            }
        """)
init python:
    import pygame
    import math
    
    renpy.register_shader("white_round", 
        variables="""
            uniform vec2 u_model_size;
            uniform float u_radius;
            uniform float u_stroke_width;
            attribute vec2 a_tex_coord;
            varying vec2 v_tex_coord;
        """,
        vertex_100="""
            v_tex_coord = a_tex_coord;
        """,
        fragment_300="""
            precision highp float;
            
            vec2 uv = v_tex_coord;
            vec2 center = vec2(0.5, 0.5);
            float dist = distance(uv, center);
            if(dist <= u_radius) {
                gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
            }
            else if(dist <= u_radius + u_stroke_width) {
                gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
            }
            else {
                gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
            }
        """)
    
    renpy.register_shader("color_round_picker", variables="""
        uniform vec2 u_model_size;
        attribute vec2 a_tex_coord;
        varying vec2 v_tex_coord;
    """, vertex_100="""
        v_tex_coord = a_tex_coord;
    """, fragment_300="""
        precision highp float;
        vec2 uv = v_tex_coord;
        vec2 center = vec2(0.5, 0.5);
        float dist = distance(uv, center);
        float outer_radius = 0.49;
        float inner_radius = 0.38;
        
        if(dist >= inner_radius && dist <= outer_radius) {
            float angle = atan(center.y - uv.y , center.x - uv.x); 
            float hue = (angle + 3.141592653589793) / (2.0 * 3.141592653589793);
            float h = hue * 6.0;
            int i = int(floor(h));
            float f = h - float(i);
            float p = 0.0;
            float q = 1.0 - f;
            float t = f;
            
            if (i == 0) {
                gl_FragColor = vec4(1.0, t, p, 1.0);
            } else if (i == 1) {
                gl_FragColor = vec4(q, 1.0, p, 1.0);
            } else if (i == 2) {
                gl_FragColor = vec4(p, 1.0, t, 1.0);
            } else if (i == 3) {
                gl_FragColor = vec4(p, q, 1.0, 1.0);
            } else if (i == 4) {
                gl_FragColor = vec4(t, p, 1.0, 1.0);
            } else {
                gl_FragColor = vec4(1.0, p, q, 1.0);
            }
        } else {
            gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
        }
    """)
    class ColorPicker(renpy.Displayable):
        def __init__(self, width=800, height=800, xpos=0.5, ypos=0.5, color_picker_zoom=1.0, step=6, **kwargs):
            super(ColorPicker, self).__init__(**kwargs)
            self.color_picker_zoom = color_picker_zoom
            self.width = int(width * color_picker_zoom)
            self.height = int(height * color_picker_zoom)
            self.xpos = xpos
            self.ypos = ypos
            
            # 计算位置
            screen_width, screen_height = renpy.get_physical_size()
            self.actual_x = int(self.xpos * (screen_width - self.width))
            self.actual_y = int(self.ypos * (screen_height - self.height))
            
            # 圆环参数
            self.center_x = self.width // 2
            self.center_y = self.height // 2
            base_radius = min(width, height) // 2
            self.outer_radius = int(base_radius * 0.98 * color_picker_zoom)
            self.inner_radius = int(base_radius * 0.74 * color_picker_zoom)
            self.slider_radius = (self.inner_radius + self.outer_radius) // 2
            
            # 矩形参数
            self.rect_width = int(420 * color_picker_zoom)
            self.rect_height = int(420 * color_picker_zoom)
            self.rect_step = step
            self.rect_x = (self.width - self.rect_width) // 2
            self.rect_y = (self.height - self.rect_height) // 2
            
            # 状态变量
            self.angle = 0.0
            self.slider_rel_x = 0.5
            self.slider_rel_y = 0.5
            self.dragging_ring = False
            self.dragging_rect = False
            self.base_color = Color("#ff0000")
            self.final_color = Color("#ff0000")
            self.rect_colors_cache = None
            self.cache_base_color = None
            
        def get_hue_color(self, angle):
            """根据角度获取HSV颜色空间的颜色"""
            hue = angle / (2 * math.pi)
            return Color(hsv=(hue, 1.0, 1.0))
            
        def get_rect_color_at_position(self, x, y):
            """获取矩形中指定位置的颜色"""
            u = max(0.0, min(1.0, x / float(self.rect_width - 1) if self.rect_width > 1 else 0))
            v = max(0.0, min(1.0, y / float(self.rect_height - 1) if self.rect_height > 1 else 0))
            
            saturation = u
            brightness = 1.0 - v
            gray = Color(rgb=(brightness, brightness, brightness))
            pure_color = self.base_color.multiply_value(brightness)
            
            return gray.interpolate(pure_color, saturation)
        def precompute_rect_colors(self):
            """预计算"""
            if self.rect_colors_cache is not None and self.cache_base_color == self.base_color:
                return self.rect_colors_cache
                
            colors = {}
            for x in range(0, self.rect_width, self.rect_step):
                for y in range(0, self.rect_height, self.rect_step):
                    colors[(x, y)] = self.get_rect_color_at_position(x, y)
            
            self.rect_colors_cache = colors
            self.cache_base_color = self.base_color
            return colors
            
        def render(self, width, height, st, at):
            render = renpy.Render(width, height)
            # 更新基色
            self.base_color = self.get_hue_color(self.angle)
            # 渲染圆环
            ring_display = Transform(Solid("#ffffff"), 
                                shader="color_round_picker", 
                                mesh=True,
                                xsize=self.width, 
                                ysize=self.height)
            ring_render = renpy.render(ring_display, width, height, st, at)
            render.blit(ring_render, (self.actual_x, self.actual_y))
            # 渲染矩形
            rect_colors = self.precompute_rect_colors()
            for (x, y), color in rect_colors.items():
                rect_width = min(self.rect_step, self.rect_width - x)
                rect_height = min(self.rect_step, self.rect_height - y)
                color_rect = Solid(color, xsize=rect_width, ysize=rect_height)
                color_render = renpy.render(color_rect, width, height, st, at)
                render.blit(color_render, (self.actual_x + self.rect_x + x, self.actual_y + self.rect_y + y))
            # 最终颜色
            self.final_color = self.get_rect_color_at_position(
                self.slider_rel_x * self.rect_width,
                self.slider_rel_y * self.rect_height
            )
            # 渲染圆环滑块
            # 这里可以调圆快和圆环的相对位置
            ring_slider_x = self.actual_x + self.center_x + (self.slider_radius+5) * math.cos(self.angle)
            ring_slider_y = self.actual_y + self.center_y + (self.slider_radius) * math.sin(self.angle)
            self._render_ring_slider(render, ring_slider_x, ring_slider_y, 
                            int(min(self.width, self.height) * 0.15 * self.color_picker_zoom),
                            width, height, st, at)
            
            # 渲染矩形滑块
            rect_slider_x = self.actual_x + self.rect_x + (self.slider_rel_x * self.rect_width)
            rect_slider_y = self.actual_y + self.rect_y + (self.slider_rel_y * self.rect_height)
            self._render_slider(render, rect_slider_x, rect_slider_y,
                            int(min(self.width, self.height) * 0.05 * self.color_picker_zoom),
                            width, height, st, at)
            
            renpy.redraw(self, 0.05)
            return render
        def _render_ring_slider(self, render, x, y, size, width, height, st, at):
            """渲染圆环滑块"""
            slider_pos_x = x - size // 2
            slider_pos_y = y - size // 2
            
            # 使用白色圆形着色器
            slider_display = Transform(Solid("#ffffff"), 
                                    shader="white_round", 
                                    mesh=True,
                                    xsize=size, 
                                    ysize=size,
                                    u_radius=0.45,
                                    u_stroke_width=0.025)
            slider_render = renpy.render(slider_display, width, height, st, at)
            render.blit(slider_render, (slider_pos_x, slider_pos_y))
            
        def _render_slider(self, render, x, y, size, width, height, st, at):
            """渲染滑块"""
            slider_pos_x = x - size // 2
            slider_pos_y = y - size // 2
            
            # 黑色边框
            slider_border = Solid("#000000", xsize=size, ysize=size)
            slider_border_render = renpy.render(slider_border, width, height, st, at)
            render.blit(slider_border_render, (slider_pos_x, slider_pos_y))
            
            # 白色内部
            inner_size = size - 4
            inner_pos_x = slider_pos_x + 2
            inner_pos_y = slider_pos_y + 2
            slider_inner = Solid("#FFFFFF", xsize=inner_size, ysize=inner_size)
            slider_inner_render = renpy.render(slider_inner, width, height, st, at)
            render.blit(slider_inner_render, (inner_pos_x, inner_pos_y))
            
        def event(self, ev, x, y, st):
            # 更新实际位置
            screen_width, screen_height = renpy.get_physical_size()
            self.actual_x = int(self.xpos * (screen_width - self.width))
            self.actual_y = int(self.ypos * (screen_height - self.height))
            
            local_x = x - self.actual_x
            local_y = y - self.actual_y
            
            # 圆环拖动
            if self._is_in_ring(local_x, local_y):
                self._handle_ring_event(ev, local_x, local_y)
            
            # 矩形拖动
            if self._is_in_rect(x, y):
                self._handle_rect_event(ev, x, y)
                
            return None
        def _is_in_ring(self, x, y):
            """检查是否在圆环内"""
            dx = x - self.center_x
            dy = y - self.center_y
            distance = math.sqrt(dx*dx + dy*dy)
            return self.inner_radius <= distance <= self.outer_radius
            
        def _is_in_rect(self, x, y):
            """检查是否在矩形内"""
            rect_actual_x = self.actual_x + self.rect_x
            rect_actual_y = self.actual_y + self.rect_y
            return (rect_actual_x <= x <= rect_actual_x + self.rect_width and 
                    rect_actual_y <= y <= rect_actual_y + self.rect_height)
            
        def _handle_ring_event(self, ev, local_x, local_y):
            dx = local_x - self.center_x
            dy = local_y - self.center_y
            angle = math.atan2(dy, dx)
            if angle < 0:
                angle += 2 * math.pi
                
            if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
                self.angle = angle
                self.dragging_ring = True
                renpy.restart_interaction()
            elif ev.type == pygame.MOUSEBUTTONUP and ev.button == 1:
                self.dragging_ring = False
            elif ev.type == pygame.MOUSEMOTION and self.dragging_ring:
                self.angle = angle
                renpy.restart_interaction()
                
        def _handle_rect_event(self, ev, x, y):
            rect_actual_x = self.actual_x + self.rect_x
            rect_actual_y = self.actual_y + self.rect_y
            
            if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
                rel_x = (x - rect_actual_x) / self.rect_width
                rel_y = (y - rect_actual_y) / self.rect_height
                self.slider_rel_x = max(0.0, min(1.0, rel_x))
                self.slider_rel_y = max(0.0, min(1.0, rel_y))
                self.dragging_rect = True
            elif ev.type == pygame.MOUSEBUTTONUP and ev.button == 1:
                self.dragging_rect = False
            elif ev.type == pygame.MOUSEMOTION and self.dragging_rect:
                rel_x = (x - rect_actual_x) / self.rect_width
                rel_y = (y - rect_actual_y) / self.rect_height
                self.slider_rel_x = max(0.0, min(1.0, rel_x))
                self.slider_rel_y = max(0.0, min(1.0, rel_y))
                renpy.restart_interaction()
            
        def visit(self):
            return []
            
        def get_final_color(self):
            return self.final_color.hexcode

缺点:(高情商:给后面的人优化空间;低情商:我太菜了,不会改
1.在设置渲染精度高了后还是会出现卡顿
2.其效果还只是能用,优化空间还有很多,主要是懒得改了(


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|RenPy中文空间 ( 苏ICP备17067825号|苏公网安备 32092302000068号 )

GMT+8, 2025-11-22 06:45 , Processed in 0.061167 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表