找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 166|回复: 3

[教程] 如何实现按键控制人物行走(CDD实现)

[复制链接]
发表于 2025-3-2 13:59:46 | 显示全部楼层 |阅读模式

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

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

×
本帖最后由 Reacted 于 2025-3-2 15:25 编辑

前言:
最近水了一下Renpy的群,发现群里在讨论有关Renpy制作RPG的想法。

正好我正在尝试使用Renpy制作Rpg。(目前实现了自由编辑有高低差的地图,卡牌战斗,人物移动)
但是 我写的一些代码还没有经过重构,所以现在不是最佳的状态,现在仅分享一下人物行走。
之前鄙人发过一个帖子,也是关于人物行走的,但是那次是用screen的timer实现的,这次分享改良后的CDD方案,希望能帮到你们。
最后:如果我的游戏有幸能做出来的话,希望大家可以支持一下。

教程:
第一步:需求分析
分析一下我们要实现的功能:人物行走
这个功能无非就是通过按键来改变人物的坐标,同时播放人物的动画

那么我们来建立一个空的CDD。

cdd.rpy:
[RenPy] 纯文本查看 复制代码
init python early:
    class WalkMan(renpy.Displayable):
        def __init__(self,**kwards):
            super().__init__(**kwards)

        def render(self,width,height,st,at):
            render = renpy.Render(width,height)
            return render
        
        def event(self,ev,x,y,st):
            pass


这样就新建了一个空的CDD。接下来我们给它初始化一下。设置一些属性。

[RenPy] 纯文本查看 复制代码
init python early:
    from enum import Enum

    #用一个枚举来记录玩家的状态
    #玩家的状态也就是走路或者不动
    #再细分一下就是左站,右站,上站,下站,左走,右走,上走,下走
    class Status(Enum):
        LEFT_STAND = 0
        RIGHT_STAND = 1
        UP_STAND = 2
        DOWN_STAND = 3

        LEFT_WALK = 4
        RIGHT_WALK = 5
        UP_WALK = 6
        DOWN_WALK = 7

    class WalkMan(renpy.Displayable):
        def __init__(self,**kwards):
            super().__init__(**kwards)
            #坐标
            self.speed = 10 #玩家速度
            self.x = 500 #x坐标
            self.y = 500 #y坐标
            self.img = Solid(color="#ff0000",xysize=(100,100)) #人物的图片,暂时用红色矩形来替代。
            self.status = Status.LEFT_STAND #状态,默认向左站

        def render(self,width,height,st,at):
            render = renpy.Render(width,height)
            return render
        
        def event(self,ev,x,y,st):
            pass


然后我们就要来通过按键来改变玩家的状态
那么我们来定义一个函数作为玩家控制器。
然后把它放进event里。
玩家控制器需要用到pygame代码,记得在上面import pygame

[RenPy] 纯文本查看 复制代码
        def event(self,ev,x,y,st):
            self.player_control(ev,x,y,st)
            
        def player_control(self,ev,x,y,st):
            #通过按键改变玩家状态

            #如果按下按键,就照着对应的方向走
            if ev.type == pygame.KEYDOWN:
                if ev.key == pygame.K_a:
                    self.status = Status.LEFT_WALK
                elif ev.key == pygame.K_d:
                    self.status = Status.RIGHT_WALK
                elif ev.key == pygame.K_w:
                    self.status = Status.UP_WALK
                elif ev.key == pygame.K_s:
                    self.status = Status.DOWN_WALK
            #如果松开按键,就照着刚刚走的方向站
            if ev.type == pygame.KEYUP:
                if self.status == Status.LEFT_WALK:
                    self.status = Status.LEFT_STAND
                elif self.status == Status.RIGHT_WALK:
                    self.status = Status.RIGHT_STAND
                elif self.status == Status.UP_WALK:
                    self.status = Status.UP_STAND
                elif self.status == Status.DOWN_WALK:
                    self.status = Status.DOWN_STAND


光是改变玩家状态还是不够的,我们还要给它加上速度。所以我们再写一个函数,用来给予速度。
那么速度是有方向的,在RPG游戏中,我们只考虑上下左右四个方向。我们把速度看成一个元组
(速度的值,速度的方向)就行了。
也就是说,速率是speed,速度是velocity。

我们修正一下之前的初始化,并引入方向和速度
[RenPy] 纯文本查看 复制代码
    class Direction(Enum):
        LEFT = 0
        RIGHT = 1
        UP = 2
        DOWN = 3

    class WalkMan(renpy.Displayable):
        def __init__(self,**kwards):
            super().__init__(**kwards)
            #坐标
            self.speed = 10 #玩家速率
            self.velocity = (0,Direction.LEFT) #玩家速度,默认是0,向左
            self.x = 500 #x坐标
            self.y = 500 #y坐标
            self.pos = self.x,self.y
            self.img = Solid(color="#ff0000",xysize=(100,100)) #人物的图片,暂时用红色矩形来替代。
            self.status = Status.LEFT_STAND #状态,默认向左走
            self.moving = False #判断玩家是否正在移动的标识符,那么开始时肯定不在移动。


然后创建给玩家速度的函数
[RenPy] 纯文本查看 复制代码
        def change_player_velocity(self):
            speed = self.speed
            #如果在走的话,施加速度
            if self.status == Status.LEFT_WALK:
                self.velocity = (speed,Direction.LEFT)
            elif self.status == Status.RIGHT_WALK:
                self.velocity = (speed,Direction.RIGHT)
            elif self.status == Status.UP_WALK:
                self.velocity = (speed,Direction.UP)
            elif self.status == Status.DOWN_WALK:
                self.velocity = (speed,Direction.DOWN)
            #如果不在走,速率就是0(速率是0的话方向就没啥关系了)
            else:
                self.velocity = (0,Direction.LEFT)


现在玩家会被按键改变状态,change_player_velocity函数会根据玩家的状态给玩家速度。
接下来,我们创建一个根据玩家速度来改变玩家坐标的函数。

说一下改变玩家坐标的思路。
首先,我们肯定希望玩家走路的速度是恒定的。
这里的恒定指的是不会随着游戏的帧率改变而改变。
假设我们直接去拿坐标加的话,x+xspeed,假设这里的速度是10
那么游戏在100帧运行的时候,一秒走的路程是 100xspeed。
游戏在30帧运行的时候,一秒走的路程是30xspeed。
也就是说,玩家实际的速度会变慢很多。
这不是我们想要看到的。
为了防止这样,我们先要获取上一次改变坐标和这一次改变坐标之间的时间--deltatime。
也就是时间的增量
然后 x = x + speed * deltatime
这样玩家走的路程就只与游戏运行时间有关,而不与游戏的帧率有关,也就是玩家的速度不会变。

为了获取时间,我们先在__init__()里加入一个新的属性:self.last_time = 0


那么符合这样要求的函数大概长这样:
[RenPy] 纯文本查看 复制代码
        def change_player_pos(self,st):
            speed = self.velocity[0]
            direction = self.velocity[1]

            #获取deltatime
            deltatime = st - self.last_time
            #更新last_time
            self.last_time = st

            #如果速度不是0,更新坐标
            if speed != 0:
                if direction == Direction.LEFT:
                    self.x += -speed * deltatime
                elif direction == Direction.RIGHT:
                    self.x += speed * deltatime
                elif direction == Direction.UP:
                    self.y += -speed * deltatime
                elif direction == Direction.DOWN:
                    self.y += speed * deltatime 



在event中加上所有我们写的函数并更新:
[RenPy] 纯文本查看 复制代码
 def event(self,ev,x,y,st):
self.player_control(ev,x,y,st)
self.change_player_velocity()
self.change_player_pos(st)
renpy.restart_interaction()



在render()中将红方块渲染出来。
[RenPy] 纯文本查看 复制代码
        def render(self,width,height,st,at):
            render = renpy.Render(width,height)

            child_render = renpy.render(self.img,width,height,st,at)
            render.blit(child_render,(self.x,self.y))

            renpy.redraw(self,0)
            return render

那么到目前位置控制玩家移动的逻辑就写好了,以下是目前的代码:
[RenPy] 纯文本查看 复制代码
init python early:
    from enum import Enum
    import pygame

    #用一个枚举来记录玩家的状态
    #玩家的状态也就是走路或者不动
    #再细分一下就是左站,右站,上站,下站,左走,右走,上走,下走
    class Status(Enum):
        LEFT_STAND = 0
        RIGHT_STAND = 1
        UP_STAND = 2
        DOWN_STAND = 3

        LEFT_WALK = 4
        RIGHT_WALK = 5
        UP_WALK = 6
        DOWN_WALK = 7

    class Direction(Enum):
        LEFT = 0
        RIGHT = 1
        UP = 2
        DOWN = 3

    class WalkMan(renpy.Displayable):
        def __init__(self,**kwards):
            super(WalkMan,self).__init__(**kwards)
            #坐标
            self.speed = 1000 #玩家速率
            self.velocity = (0,Direction.LEFT) #玩家速度,默认是0,向左
            self.x = 500 #x坐标
            self.y = 500 #y坐标
            self.pos = self.x,self.y
            self.img = Solid(color="#ff0000",xysize=(100,100)) #人物的图片,暂时用红色矩形来替代。
            self.status = Status.LEFT_STAND #状态,默认向左走
            self.last_time = 0

        def render(self,width,height,st,at):
            render = renpy.Render(width,height)

            child_render = renpy.render(self.img,width,height,st,at)
            render.blit(child_render,(self.x,self.y))

            renpy.redraw(self,0)
            return render
        
        def event(self,ev,x,y,st):
            self.player_control(ev,x,y,st)
            self.change_player_velocity()
            self.change_player_pos(st)
            renpy.restart_interaction()
            
        def player_control(self,ev,x,y,st):
            #通过按键改变玩家状态
            #如果按下按键,就照着对应的方向走
            if ev.type == pygame.KEYDOWN:
                if ev.key == pygame.K_a:
                    self.status = Status.LEFT_WALK
                elif ev.key == pygame.K_d:
                    self.status = Status.RIGHT_WALK
                elif ev.key == pygame.K_w:
                    self.status = Status.UP_WALK
                elif ev.key == pygame.K_s:
                    self.status = Status.DOWN_WALK
            #如果松开按键,就照着刚刚走的方向站
            if ev.type == pygame.KEYUP:
                if self.status == Status.LEFT_WALK:
                    self.status = Status.LEFT_STAND

                elif self.status == Status.RIGHT_WALK:
                    self.status = Status.RIGHT_STAND

                elif self.status == Status.UP_WALK:
                    self.status = Status.UP_STAND

                elif self.status == Status.DOWN_WALK:
                    self.status = Status.DOWN_STAND

        def change_player_velocity(self):
            speed = self.speed
            #如果在走的话,施加速度
            if self.status == Status.LEFT_WALK:
                self.velocity = (speed,Direction.LEFT)
            elif self.status == Status.RIGHT_WALK:
                self.velocity = (speed,Direction.RIGHT)
            elif self.status == Status.UP_WALK:
                self.velocity = (speed,Direction.UP)
            elif self.status == Status.DOWN_WALK:
                self.velocity = (speed,Direction.DOWN)
            #如果不在走,速率就是0(速率是0的话方向就没啥关系了)
            else:
                self.velocity = (0,Direction.LEFT)
        
        def change_player_pos(self,st):
            speed = self.velocity[0]
            direction = self.velocity[1]

            #获取deltatime
            deltatime = st - self.last_time
            #更新last_time
            self.last_time = st

            #如果速度不是0,更新坐标
            if speed != 0:
                if direction == Direction.LEFT:
                    self.x += -speed * deltatime
                elif direction == Direction.RIGHT:
                    self.x += speed * deltatime
                elif direction == Direction.UP:
                    self.y += -speed * deltatime
                elif direction == Direction.DOWN:
                    self.y += speed * deltatime 
            

            

        


                


我们建一个场景来测试一下

script.rpy:
[RenPy] 纯文本查看 复制代码
default wm = WalkMan()
label start:
    jump test
    return

label test:
    call screen test

screen test:
    add wm


    


运行游戏,发现红方块。按wasd可以移动它

接下来我们来渲染走路的贴图。
这里是我的文件,我比较懒,就只做两个方向了,其实做四个方向也是一样的。

                               
登录/注册后可看大图

建立新的img属性

[RenPy] 纯文本查看 复制代码
            self.left_img = ["stand_left.png","walk_left_1.png","walk_left_2.png"]
            self.right_img = ["stand_right.png","walk_right_1.png","walk_right_2.png"]
            self.img = self.left_img[0]



加一个走路时间的属性
[RenPy] 纯文本查看 复制代码
            self.walk_time = 0



这里我们假设0.3秒切换一次图片好了。
那么三张图也就是三个阶段
0秒-0.3秒
0.3-0.6秒
0.6-0.9秒。
写一个更新走路时间的函数。
[RenPy] 纯文本查看 复制代码
        def update_walk_time(self,deltatime):
            speed = self.velocity[0]
            #如果走路时间大于0.9秒就归零
            if self.walk_time > 0.9:
                self.walk_time = 0 

            self.walk_time += deltatime



刚刚我们的change_player_pos函数里有deltatime,我们把这个函数放在里面一起跑一下(这样是不好的)
[RenPy] 纯文本查看 复制代码
        def change_player_pos(self,st):
            speed = self.velocity[0]
            direction = self.velocity[1]

            #获取deltatime
            deltatime = st - self.last_time
            #更新last_time
            self.last_time = st

            self.update_walk_time(deltatime)

            #如果速度不是0,更新坐标
            if speed != 0:
                if direction == Direction.LEFT:
                    self.x += -speed * deltatime
                elif direction == Direction.RIGHT:
                    self.x += speed * deltatime
                elif direction == Direction.UP:
                    self.y += -speed * deltatime
                elif direction == Direction.DOWN:
                    self.y += speed * deltatime 




然后我们来写选择图片的方法。(写的有点乱,见谅)
[RenPy] 纯文本查看 复制代码
        def update_walk_img(self):

            if self.status == Status.LEFT_STAND:
                self.img = self.left_img[0]
            elif self.status == Status.RIGHT_STAND:
                self.img = self.right_img[0]

            elif self.status == Status.RIGHT_WALK:
                if self.walk_time<0.3:
                    self.img = self.right_img[0]
                elif 0.3<self.walk_time<=0.6:
                    self.img = self.right_img[1]
                elif 0.6<self.walk_time<=0.9:
                    self.img = self.right_img[2]
            
            elif self.status == Status.LEFT_WALK:
                if self.walk_time<0.3:
                    self.img = self.left_img[0]
                elif 0.3<self.walk_time<=0.6:
                    self.img = self.left_img[1]
                elif 0.6<self.walk_time<=0.9:
                    self.img = self.left_img[2]



加入到event
[RenPy] 纯文本查看 复制代码
        def event(self,ev,x,y,st):
            self.player_control(ev,x,y,st)
            self.change_player_velocity()
            self.change_player_pos(st)
            self.update_walk_img()
            renpy.restart_interaction()



再次打开游戏测试,发现已经完成了。
附上所有代码


script.rpy:
[RenPy] 纯文本查看 复制代码
default wm = WalkMan()
label start:
    jump test
    return

label test:
    call screen test

screen test:
    add wm


    


cdd.rpy:
[RenPy] 纯文本查看 复制代码
init python early:
    from enum import Enum
    import pygame

    #用一个枚举来记录玩家的状态
    #玩家的状态也就是走路或者不动
    #再细分一下就是左站,右站,上站,下站,左走,右走,上走,下走
    class Status(Enum):
        LEFT_STAND = 0
        RIGHT_STAND = 1
        UP_STAND = 2
        DOWN_STAND = 3

        LEFT_WALK = 4
        RIGHT_WALK = 5
        UP_WALK = 6
        DOWN_WALK = 7

    class Direction(Enum):
        LEFT = 0
        RIGHT = 1
        UP = 2
        DOWN = 3

    class WalkMan(renpy.Displayable):
        def __init__(self,**kwards):
            super(WalkMan,self).__init__(**kwards)
            #坐标
            self.speed = 500 #玩家速率
            self.velocity = (0,Direction.LEFT) #玩家速度,默认是0,向左
            self.x = 500 #x坐标
            self.y = 500 #y坐标
            self.pos = self.x,self.y
            self.left_img = ["stand_left.png","walk_left_1.png","walk_left_2.png"]
            self.right_img = ["stand_right.png","walk_right_1.png","walk_right_2.png"]
            self.img = self.left_img[0]
            self.status = Status.LEFT_STAND #状态,默认向左走
            self.last_time = 0
            self.walk_time = 0

        def render(self,width,height,st,at):
            render = renpy.Render(width,height)
            img = Image(self.img)
            child_render = renpy.render(img,width,height,st,at)
            render.blit(child_render,(self.x,self.y))

            renpy.redraw(self,0)
            return render
        
        def event(self,ev,x,y,st):
            self.player_control(ev,x,y,st)
            self.change_player_velocity()
            self.change_player_pos(st)
            self.update_walk_img()
            renpy.restart_interaction()
            
        def player_control(self,ev,x,y,st):
            #通过按键改变玩家状态
            #如果按下按键,就照着对应的方向走
            if ev.type == pygame.KEYDOWN:
                if ev.key == pygame.K_a:
                    self.status = Status.LEFT_WALK
                elif ev.key == pygame.K_d:
                    self.status = Status.RIGHT_WALK
                elif ev.key == pygame.K_w:
                    self.status = Status.UP_WALK
                elif ev.key == pygame.K_s:
                    self.status = Status.DOWN_WALK
            #如果松开按键,就照着刚刚走的方向站
            if ev.type == pygame.KEYUP:
                if self.status == Status.LEFT_WALK:
                    self.status = Status.LEFT_STAND

                elif self.status == Status.RIGHT_WALK:
                    self.status = Status.RIGHT_STAND

                elif self.status == Status.UP_WALK:
                    self.status = Status.UP_STAND

                elif self.status == Status.DOWN_WALK:
                    self.status = Status.DOWN_STAND

        def change_player_velocity(self):
            speed = self.speed
            #如果在走的话,施加速度
            if self.status == Status.LEFT_WALK:
                self.velocity = (speed,Direction.LEFT)
            elif self.status == Status.RIGHT_WALK:
                self.velocity = (speed,Direction.RIGHT)
            elif self.status == Status.UP_WALK:
                self.velocity = (speed,Direction.UP)
            elif self.status == Status.DOWN_WALK:
                self.velocity = (speed,Direction.DOWN)
            #如果不在走,速率就是0(速率是0的话方向就没啥关系了)
            else:
                self.velocity = (0,Direction.LEFT)
        
        def change_player_pos(self,st):
            speed = self.velocity[0]
            direction = self.velocity[1]

            #获取deltatime
            deltatime = st - self.last_time
            #更新last_time
            self.last_time = st

            self.update_walk_time(deltatime)

            #如果速度不是0,更新坐标
            if speed != 0:
                if direction == Direction.LEFT:
                    self.x += -speed * deltatime
                elif direction == Direction.RIGHT:
                    self.x += speed * deltatime
                elif direction == Direction.UP:
                    self.y += -speed * deltatime
                elif direction == Direction.DOWN:
                    self.y += speed * deltatime 
        
        def update_walk_time(self,deltatime):
            speed = self.velocity[0]
            #如果走路时间大于0.9秒就归零
            if self.walk_time > 0.9:
                self.walk_time = 0 

            self.walk_time += deltatime
        
        def update_walk_img(self):

            if self.status == Status.LEFT_STAND:
                self.img = self.left_img[0]
            elif self.status == Status.RIGHT_STAND:
                self.img = self.right_img[0]

            elif self.status == Status.RIGHT_WALK:
                if self.walk_time<0.3:
                    self.img = self.right_img[0]
                elif 0.3<self.walk_time<=0.6:
                    self.img = self.right_img[1]
                elif 0.6<self.walk_time<=0.9:
                    self.img = self.right_img[2]
            
            elif self.status == Status.LEFT_WALK:
                if self.walk_time<0.3:
                    self.img = self.left_img[0]
                elif 0.3<self.walk_time<=0.6:
                    self.img = self.left_img[1]
                elif 0.6<self.walk_time<=0.9:
                    self.img = self.left_img[2]
                


            

            

            

        


                













发表于 2025-3-2 14:26:48 | 显示全部楼层
本帖最后由 leech 于 2025-3-2 14:28 编辑

刚连夜丢完移动框架的屎就被大佬连夜抬走

                               
登录/注册后可看大图
回复 支持 抱歉

使用道具 举报

发表于 2025-3-2 14:28:42 | 显示全部楼层
太帅了,感谢大佬们分享,先赞后学了
回复 支持 抱歉

使用道具 举报

发表于 2025-3-3 14:19:38 | 显示全部楼层
收藏了 我学我学
回复 支持 抱歉

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-3-12 17:26 , Processed in 0.052068 second(s), 24 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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