马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 Maz马 于 2025-10-13 18:31 编辑
叠甲
首先,我的编程基础为0,以下为纯摸索经验,如果有不对的地方望轻喷
基于个人需求写的东西
拉都拉了,就把这盘九转大肠捧出来给大家品鉴一下
事情的起因是这样的,我一直想做ACT游戏,奈何水平有限,不会py,用screen硬堆了一个角色移动系统
简单rpg地图移动
https://www.renpy.cn/forum.php?mod=viewthread&tid=1652
(出处: RenPy中文空间)
然后因为当时群里讨论剧烈,大佬发布了他的码
如何实现按键控制人物行走(CDD实现)
https://www.renpy.cn/forum.php?mod=viewthread&tid=1654
(出处: RenPy中文空间)
因为这篇我认为还是挺亲我这种没有基础的愚民的,让我看到了“啊!我好像也不是学不会”的希望,然后我就开始暴学了cdd
以下是经验分享:
首先先说0基础的方面,曾经也有大佬推荐我看py文档,奈何基础很烂,真的一大半都看不懂,就在我半夜痛哭流涕刷小说的时候
我发现了有一本书“看漫画学PY”,书中言语尽是幼儿园水平,我反复看了两天,终于理解了“面向对象”“类”“函数”的基础概念
然后我就开始试图着手修改大佬的行走CDD顺带学习,让它成为我的样子
能稍微看懂函数和类之后,修改并不困难,因为我只是想把平面行走更改成横板而已
以下是理解:
[RenPy] 纯文本查看 复制代码 #定义一只狗,“类”型为狗(cdd)
default bigdog = Dog()
#这个是起手
init python:
#这个是你创建的CDD,继承于renpy.Displayable(也就是留给你的接口)
class Dog(renpy.Displayable):
def __init__(self,**kwards):#这里填你传进来的参数
super().__init__(**kwards)
########
#这里填你要用到的量
self.abc = 123
########
#这个是渲染器》图像显示
def render(self,width,height,st,at):
render = renpy.Render(width,height)
return render
#这个是控制器》接收交互(键盘,点击,拖拽之类)
def event(self,ev,x,y,st):
pass
#这里是其他随你写的函数什么的(数值计算什么的,反正就是调用来调用去)
#def xxx()
然后,这只狗就是像 image 一样随你到处贴的可视组件了
在我更改大佬的行走CDD之后,我想要写一个虚拟摇杆来传递方向给这个行走的CDD
因为安卓端并没有键盘可以按,于是我写好了(在最下方我会放出)
但是在向行走CDD传递方向时,出现了摇杆无法交互的问题,然后我就开始往群里抱大腿
在热心群友@Koji 的帮助下发现了问题出在这一条里
[RenPy] 纯文本查看 复制代码 renpy.restart_interaction()
这一条函数的作用是刷新屏幕,但他的副作用是会重置init(不太记得是不是这么说)而且拦截鼠标
如果想要刷新界面应该用
[RenPy] 纯文本查看 复制代码 renpy.redraw(self,0)
这一条是只刷新可视组件
我进行了修改,然后新问题出现,使用这一条会出现坐标乱飞,丢帧的情况
我开始重构,放弃原有的代码,在好几天东抄西抄,轮流折磨G小姐D小姐之后
我得到了一个结构
[RenPy] 纯文本查看 复制代码 #定义一只狗,“类”型为狗(cdd)
default bigdog = Dog()
#这个是起手
init python:
#这个是你创建的CDD,继承于renpy.Displayable(也就是留给你的接口)
class Dog(renpy.Displayable):
def __init__(self,**kwards):#这里填你传进来的参数
super().__init__(**kwards)
########
#这里填你要用到的量
self.abc = 123
########
#这个是渲染器》图像显示
def render(self,width,height,st,at):
render = renpy.Render(width,height)
###################################<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# 需要实时渲染的动画或函数调用 #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
###################################<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇
renpy.redraw(self,0)#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
return render
#这个是控制器》接收交互(键盘,点击,拖拽之类)
def event(self,ev,x,y,st):
pass
#这里是其他随你写的函数什么的(数值计算什么的,反正就是调用来调用去)
#def xxx()
大概就是这样子,把所有需要实时渲染的动画,数值更新的函数调用放在“渲染器”或“控制器”下,最后再让它每帧重绘可视组件
成功的让它不会丢帧,坐标在后台乱飞(个人建议是复杂计算的放在”控制器“简单的放在”渲染器“)
在尝试好几次后,个人认为renpy.redraw(self,0)放在“渲染器”是最好的,因为它似乎是“终点”
另外,关于实时动画的思路我是参照了“行走CDD”的码
使用帧间隔来计算,这样子不会因为设备FPS不同而导致实时动画产生时间差或后台的坐标乱飞
然后,熬了好几天试错修改,我已经把我的ACT框架写好了,成就感挺高的,所以写了个贴,如果有谬误,也请指正
最后是附上虚拟摇杆的CDD
[RenPy] 纯文本查看 复制代码 # BY MAZ ACTSYS
# 输入模块 适配安卓的虚拟摇杆及键盘控制
init python:
#移除原生相应的快捷键
config.keymap["director"].remove("noshift_K_d")
config.keymap["screenshot"].remove("noshift_K_s")
config.keymap["toggle_fullscreen"].remove("noshift_K_f")
config.keymap["dismiss"].append("noshift_K_f")
config.keymap["dismiss"].remove("K_SPACE")
config.keymap["focus_left"].append("anyrepeat_K_a")
config.keymap["focus_right"].append("anyrepeat_K_d")
config.keymap["focus_up"].append("anyrepeat_K_w")
config.keymap["focus_down"].append("anyrepeat_K_s")
config.keymap["button_select"].append("noshift_K_f")
import math
import pygame
# 素材自适应的功能函数
# 获取显示对象的实际尺寸,如果无法获取则返回默认尺寸
def get_displayable_size(displayable, default_size):
if displayable is None:
return default_size
try:
render = renpy.render(displayable, 0, 0, 0, 0)
size = render.get_size()
return size if size[0] > 0 and size[1] > 0 else default_size
except:
return default_size
class VirtualJoystick(renpy.Displayable):
# 默认键位 将字符串按键映射到pygame常量
DEFAULT_KEYMAP = {
"w": pygame.K_w,
"a": pygame.K_a,
"s": pygame.K_s,
"d": pygame.K_d,
"q": pygame.K_q,
"f": pygame.K_f,
}
def __init__(self,vjoy_bg=None,vjoy_fg=None,button_01_img=None,button_02_img=None,vjoypos=(100,700),b01pos=(1500,800),b02pos=(1600,800),keymap=None,**kwargs):
super(VirtualJoystick,self).__init__(**kwargs)
# 摇杆外观
self.vjoy_bg = vjoy_bg or Solid("#66666666",xysize=(200,200))
self.vjoy_fg = vjoy_fg or Solid("#ffffffcc",xysize=(50,50))
self.button_01_normal = button_01_img or Solid("#666666cc",xysize=(70,70))
self.button_02_normal = button_02_img or Solid("#666666cc",xysize=(70,70))
self.button_01_pressed = Transform(self.button_01_normal,matrixcolor=BrightnessMatrix(-0.1))
self.button_02_pressed = Transform(self.button_02_normal,matrixcolor=BrightnessMatrix(-0.1))
# 尺寸设置
self.vjoy_bg_size = 200
self.vjoy_fg_size = 50
self.vjoy_pos = vjoypos
self.button_01_size = 70
self.button_01_pos = b01pos
self.button_01_text = 0
self.button_02_size = 70
self.button_02_pos = b02pos
self.button_02_text = 0
self.radius = (self.vjoy_bg_size - self.vjoy_fg_size) // 2
self.deadzone_radius = self.radius * 0.2#缓冲区
# 状态变量
self.dragging = False
self.position = (0,0)
self.offset = (0,0)
# 键位配置
self.keymap = keymap or self.DEFAULT_KEYMAP.copy()
# 初始化按键状态(使用逻辑键名)
self.vjoy_keys = {k: 0 for k in self.DEFAULT_KEYMAP.keys()}
self.button_01 = False
self.button_02 = False
#自适应渲染检查
self.sss_vjoy_check = False
def render(self,width,height,st,at):
# 首次渲染时检查尺寸
# 注意,这个尺寸并不影响图片的渲染,而是使用传入图片的尺寸作为摇杆的响应区域
if not self.sss_vjoy_check:
# 重新获取尺寸
self.vjoy_bg_size = get_displayable_size(self.vjoy_bg,(200,200))[0]
self.vjoy_fg_size = get_displayable_size(self.vjoy_fg,(50,50))[0]
self.button_01_size = get_displayable_size(self.button_01_normal,(70,70))[0]
self.button_02_size = get_displayable_size(self.button_02_normal,(70,70))[0]
# 更新相关计算
self.radius = (self.vjoy_bg_size - self.vjoy_fg_size) // 2
self.deadzone_radius = self.radius * 0.2
self.sss_vjoy_check = True
render = renpy.Render(width,height)
# 渲染背景
vjoy_bg_render = renpy.render(self.vjoy_bg,width,height,st,at)
render.blit(vjoy_bg_render,(self.vjoy_pos[0],self.vjoy_pos[1]))
# 渲染摇杆
vjoy_fg_render = renpy.render(self.vjoy_fg,width,height,st,at)
# displayable.render是displayable自身的,所以他的width,height会影响图片的渲染情况
# renpy.render是renpy的,并不会影响调用的displayable,而是划了一块区域给displayable用
# 这个区域并不影响渲染,因为渲染由displayable自身的render决定
#
# displayable.render中width,height传入的是图像尺寸。是渲染函数
# renpy.render中width,height是组件的尺寸。是类似布局系统
# 这里的逻辑我还没有彻底理清楚,但最终需要通过render()函数返回一个Render()对象
vjoy_fg_x = self.vjoy_pos[0] + (self.vjoy_bg_size - self.vjoy_fg_size) // 2 + self.position[0]
vjoy_fg_y = self.vjoy_pos[1] + (self.vjoy_bg_size - self.vjoy_fg_size) // 2 + self.position[1]
render.blit(vjoy_fg_render,(vjoy_fg_x,vjoy_fg_y))
# 渲染Q按钮
button_01 = self.button_01_pressed if self.button_01 else self.button_01_normal
q_render = renpy.render(button_01,width,height,st,at)
render.blit(q_render,(self.button_01_pos[0],self.button_01_pos[1]))
# 渲染Q按钮文本
if self.button_01_text >= 0:
q_text_render = renpy.render(Text(str(format(self.button_01_text, '.2f'))),width,height,st,at)
q_text_width, q_text_height = q_text_render.get_size()
q_draw_x = self.button_01_pos[0] + (self.button_01_size - q_text_width) / 2
q_draw_y = self.button_01_pos[1] + (self.button_01_size - q_text_height) / 2
render.blit(q_text_render,(q_draw_x,q_draw_y))
# 渲染F按钮
button_02 = self.button_02_pressed if self.button_02 else self.button_02_normal
f_render = renpy.render(button_02,width,height,st,at)
render.blit(f_render,(self.button_02_pos[0],self.button_02_pos[1]))
# 渲染Q按钮文本
if self.button_02_text >= 0:
f_text_render = renpy.render(Text(str(format(self.button_02_text, '.2f'))),width,height,st,at)
f_text_width, f_text_height = f_text_render.get_size()
f_draw_x = self.button_02_pos[0] + (self.button_02_size - f_text_width) / 2
f_draw_y = self.button_02_pos[1] + (self.button_02_size - f_text_height) / 2
render.blit(f_text_render,(f_draw_x,f_draw_y))
#调试说明
if config.developer:
debug = Text("W: [vjoy.vjoy_keys['w']] A: [vjoy.vjoy_keys['a']] S: [vjoy.vjoy_keys['s']] D: [vjoy.vjoy_keys['d']] Q: [vjoy.vjoy_keys['q']] F: [vjoy.vjoy_keys['f']]")
debug_render = renpy.render(debug,width,height,st,at)
render.blit(debug_render,(0,0))
renpy.redraw(self,0)
return render
def event(self,ev,x,y,st):
# 按钮位置
button_01_rect = (self.button_01_pos[0],self.button_01_pos[1],self.button_01_size,self.button_01_size)
button_02_rect = (self.button_02_pos[0],self.button_02_pos[1],self.button_02_size,self.button_02_size)
# 摇杆偏移位置
center_x = self.vjoy_pos[0] + self.vjoy_bg_size // 2
center_y = self.vjoy_pos[1] + self.vjoy_bg_size // 2
distance = ((x - center_x)**2 + (y - center_y)**2) ** 0.5
# 处理鼠标事件
if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
# 点击按钮01
if (self.button_01_pos[0] <= x < self.button_01_pos[0] + self.button_01_size and
self.button_01_pos[1] <= y < self.button_01_pos[1] + self.button_01_size):
self.button_01 = True
self.vjoy_keys["q"] = 1
# 点击按钮02
if (self.button_02_pos[0] <= x < self.button_02_pos[0] + self.button_02_size and
self.button_02_pos[1] <= y < self.button_02_pos[1] + self.button_02_size):
self.button_02 = True
self.vjoy_keys["f"] = 1
# 点击摇杆
if distance <= self.radius + self.vjoy_fg_size//2:
self.dragging = True
self.offset = (x - center_x,y - center_y)
return None
elif ev.type == pygame.MOUSEBUTTONUP and ev.button == 1:
# 释放按钮01
if self.button_01:
self.button_01 = False
self.vjoy_keys["q"] = 0
# 释放按钮02
if self.button_02:
self.button_02 = False
self.vjoy_keys["f"] = 0
# 释放摇杆
if self.dragging:
self.dragging = False
self.position = (0,0)
self.offset = (0,0)
# 重置方向键状态(使用字符串键名)
for key in ["w","a","s","d"]:
self.vjoy_keys[key] = 0
return None
# 拖拽摇杆
elif ev.type == pygame.MOUSEMOTION and self.dragging:
rel_x = x - center_x - self.offset[0]
rel_y = y - center_y - self.offset[1]
distance = (rel_x**2 + rel_y**2) ** 0.5
if distance > self.radius:
rel_x = rel_x * self.radius / distance
rel_y = rel_y * self.radius / distance
self.position = (rel_x,rel_y)
# 先重置所有方向键状态
for key in ["w","a","s","d"]:
self.vjoy_keys[key] = 0
# 角度计算
if distance > self.deadzone_radius:
angle = math.degrees(math.atan2(-rel_y,rel_x))
angle = (angle + 360) % 360
# 根据角度设置方向键
if 22.5 <= angle < 67.5: # 右上
self.vjoy_keys["w"] = 1
self.vjoy_keys["d"] = 1
elif 67.5 <= angle < 112.5: # 上
self.vjoy_keys["w"] = 1
elif 112.5 <= angle < 157.5: # 左上
self.vjoy_keys["w"] = 1
self.vjoy_keys["a"] = 1
elif 157.5 <= angle < 202.5: # 左
self.vjoy_keys["a"] = 1
elif 202.5 <= angle < 247.5: # 左下
self.vjoy_keys["a"] = 1
self.vjoy_keys["s"] = 1
elif 247.5 <= angle < 292.5: # 下
self.vjoy_keys["s"] = 1
elif 292.5 <= angle < 337.5: # 右下
self.vjoy_keys["d"] = 1
self.vjoy_keys["s"] = 1
else: # 右
self.vjoy_keys["d"] = 1
# 当摇杆没有被拖拽时才检查键盘,避免数据过快刷新且重复覆盖
if not self.dragging:
# 键盘事件处理(统一使用键名字符串)
keys = pygame.key.get_pressed()
for logic_key,pygame_key in self.keymap.items():
self.vjoy_keys[logic_key] = 1 if keys[pygame_key] else 0
return None
# 最终得到一个字典,self.vjoy_keys = {"w":0,"a":0,"s":0,"d":0,"q":0,"f":0,},0是未被按下,1是被按下了
default vjoy = VirtualJoystick()
#一共9个参数,摇杆区域背景图片,摇杆图片,Q按键图片,F按键图片,摇杆整体坐标(左上角),Q按钮坐标(左上角),F按钮坐标(左上角)
#示例,图片得传入renpy.displayable("xxx")
#default vjoy = VirtualJoystick(
# renpy.displayable("xxx"),
# renpy.displayable("xxx"),
# renpy.displayable("xxx"),
# renpy.displayable("xxx"),
# (x,y),
# (x,y),
# (x,y),
# keymap= 一个对应按键列表,可自定义传入键位
# callback= 一个回调函数,应传入可以响应虚拟摇杆操作的事件方法
#)
# 示例使用
#screen virtual_joystick():
# add vjoy
#
#label start:
# call screen virtual_joystick
#查看我写的更多屎
在回放中影响当前游戏存档数据的方法
https://www.renpy.cn/forum.php?mod=viewthread&tid=1739
她的心里话!隐藏文本!
https://www.renpy.cn/forum.php?mod=viewthread&tid=1713
能力雷达图
https://www.renpy.cn/forum.php?mod=viewthread&tid=1719
暴学CDD的十四天/附虚拟摇杆
https://www.renpy.cn/forum.php?mod=viewthread&tid=1675
简单rpg地图移动
https://www.renpy.cn/forum.php?mod=viewthread&tid=1652
摇骰子小游戏
https://www.renpy.cn/forum.php?mod=viewthread&tid=1653
猜球盅小游戏
https://www.renpy.cn/forum.php?mod=viewthread&tid=1574
FontGroup() 游戏里全局使用两种字体的方法
https://www.renpy.cn/forum.php?mod=viewthread&tid=1491
粉身碎骨浑不怕,要留答辩在人间
|