美文网首页
Houdini日记 | 闲得蛋疼之可视化小甲鱼吃鱼小游戏

Houdini日记 | 闲得蛋疼之可视化小甲鱼吃鱼小游戏

作者: 大师的学徒 | 来源:发表于2020-08-22 01:13 被阅读0次

    前天晚上睡前突然想起了当初接触Python时的灵魂导师小甲鱼,然后想到了他的一道经典课后题。

    游戏编程:

    按以下要求定义一个乌龟类和鱼类并尝试编写游戏。
    假设游戏场景为范围(x, y)为0<=x<=10,0<=y<=10
    游戏生成1只乌龟和10条鱼
    它们的移动方向均随机
    乌龟的最大移动能力是2(Ta可以随机选择1还是2移动),鱼儿的最大移动能力是1
    当移动到场景边缘,自动向反方向移动
    乌龟初始化体力为100(上限)
    乌龟每移动一次,体力消耗1
    当乌龟和鱼坐标重叠,乌龟吃掉鱼,乌龟体力增加20
    鱼暂不计算体力
    当乌龟体力值为0(挂掉)或者鱼儿的数量为0游戏结束。

    思路分析

    首先可以确定的是这种需要过程递推的方式在solver内进行。

    那么在solver之前需要解决:

    1.随机分布

    可以把乌龟和鱼抽象成两组点,用pointWangle在 0-10的范围内撒点。同时要注意坐标取整问题,可以借助floor()函数。(如果有round()可以四舍五入就好了)

    Solver内需要解决:

    2.随机移动

    因为结果需要用关键帧递推来实现,所以随机移动的随机seed也可以用当前帧数也就是@Frame,作为seed。
    rand()返回的随机数是[0,1] 的浮点值,借助fit01() 适配到 [-1,1] 或者[-2,2] 同时用floor()取整就可以模拟乌龟和鱼的移动步长了。

    3.体力值,移动消耗

    可视化的体力值可以用pscale来表现,暂定初始值为1,solver的每一次运算, pscale -= 0.01。

    4.乌龟吃鱼

    只要判断鱼的点和乌龟的点坐标是否重合就可以了,如果相同:

    1. 乌龟的体力值pscale += 0.2 ,
      2.鱼的点被去除,这里用到removepoint() 。
      如果不相同则继续进行下一轮推导。


    Ok, let's dive into Houdini!

    首先是在空白的集合体内撒点,这里用到pointWrangle。

    float seed = chf('random_seed');
    for(int i=0; i<10; i++){
        int fishX = floor((rand(i+seed)*10));
        int fishZ = floor((rand(i+134+seed)*10));
        
        vector fishP = set(fishX,0,fishZ);
        addpoint(0, fishP);
    }
    

    注:直接创建wrangle并用addpoint()理论上是不会出现点的,因为所有的wrangle都需要输入端。
    再注:同样的seed返回的随机数是相同的,这里用手动添加seed和增加常量的方法,目的是让fishX和fishZ的值有所区别。

    需要将Run Over从point 改为Detail once

    同样的方法创建乌龟的点组,借助attribute create为乌龟组添加pscale属性,默认更改为1,同时为小鱼组添加一个随机0.2-0.4的pscale,主要是视觉上比较好看。


    为两组点定义组名 geometry spreadsheet观察参数变化

    从这里可以发现,最后一个永远都是乌龟,前面的是小鱼。那么只要通过npoint()来查找总点数-1 = 乌龟的点序列。

    接下来进入solver开始推导,Solver主要解决三个问题,

    1.鱼的移动,每次在[-1,1]的整数值间选择;
    2.乌龟的移动,每次在[-2,2]的整数值间随机选择,且伴随体力消耗-0.01;
    3.乌龟吃鱼,当乌龟点和小鱼点重合时,小鱼点被剔除,且乌龟体力 +0.2;
    需要解决一个问题,鱼和乌龟的移动是二维的,如何选定到底是在X轴还是在Z轴移动?这里可以基于当前帧做随机数,然后取整返回0或者1,定义一下0代表移动X,1代表移动Z。
    同时要注意,rand(@Frame) 返回的值是在[0,1]区间内的,如果直接floor()取整,获得1的概率非常小,显然不公平。所以这里需要将 [0,1]区间扩大到[0,2],此时再用floor(),返回0和1的概率就相同了。

    结构图

    首先是鱼的游动问题,选定fish所在分组,分别将X轴和Z轴移动作为属性捆绑到每个点,通过随机数配合不同的seed来完成随机筛选,这里额外定义了一个@first属性,用来控制在X轴或Z轴移动。
    然后需要判断是否存在“碰壁”情况。如果在0位置且移动数值是-1,则返回0-(-1) ,如果是在9位置且移动数值为1,则返回9-(1),在0和9之间,就直接简单粗暴 P.x + stepX就好了。

    小鱼移动代码如下
    i@stepX = floor(fit01(rand(@Frame*(@ptnum+1)+1231235)*2,-1,1));
    i@stepZ = floor(fit01(rand(@Frame*(@ptnum+1)+1235)*2,-1,1));
    i@first = floor(fit01(rand(@Frame*(@ptnum+1)+12345)*2,0,1));
    
    //first = 0 ---------------> moveX
    if(@first == 0){
        i@stepZ =0;
        
        if(@P.x <= 0 && @stepX < 0){
            @P.x -= @stepX;
        }
        if(@P.x >= 9 && @stepX > 0){
            @P.x -= @stepX;
        }  
        if( 0<@P.x<9){
            @P.x += @stepX;
        }
    }
    
    //first = 1 ---------------> moveZ
    if(@first == 1){
        i@stepX = 0;
        
        if(@P.z <= 0 && @stepZ < 0){
            @P.z -= @stepZ;
        }
        if(@P.z >= 9 && @stepZ > 0){
            @P.z -= @stepZ;
        }  
        if( 0 < @P.z < 9){
            @P.z += @stepZ;
        }
    }
    

    同样的,乌龟移动的代码也可以在小鱼的基础上更改,只需要把数值范围扩大到[-2,2]就好了,这里记得更改一下随机seed,以便减少鱼和乌龟移动路径的重复性。同时不要忘记勾选wrangle针对的乌龟组。(此处更加明白了面向对象编程的重要性)

    乌龟移动代码如下
    i@stepX = floor(fit01(rand(@Frame*(@ptnum+1)+123123565)*2,-1,1));
    i@stepZ = floor(fit01(rand(@Frame*(@ptnum+1)+123235)*2,-1,1));
    i@first = floor(fit01(rand(@Frame*(@ptnum+1)+12234345)*2,0,1));
    
    //first = 0 ---------------> moveX
    if(@first == 0){
        i@stepZ =0;
        
        if(@P.x <= 0 && @stepX < 0){
            @P.x -= @stepX;
        }
        if(@P.x >= 9 && @stepX > 0){
            @P.x -= @stepX;
        }  
        if( 0<@P.x<9){
            @P.x += @stepX;
        }
    }
    //first = 1 ---------------> moveZ
    if(@first == 1){
        i@stepX = 0;
        
        if(@P.z <= 0 && @stepZ < 0){
            @P.z -= @stepZ;
        }
        if(@P.z >= 9 && @stepZ > 0){
            @P.z -= @stepZ;
        }  
        if( 0 < @P.z < 9){
            @P.z += @stepZ;
        }
    }
    //pscale ---------------> -0.01
    f@pscale -= 0.01;
    
    注:不要忘记乌龟是有体力值的!每次移动体力值-0.01。
    接下来就是乌龟吃鱼的部分了!

    乌龟如果没吃到鱼自然不需要有什么运算,直接继续解算继续走就可以了。
    如果乌龟吃到了鱼,首先乌龟体力值+0.2,然后小鱼被从几何体中剔除。
    因为是在乌龟和鱼都存在的几何体层面工作,分组筛选已经行不通了,但是!之前我们说过乌龟肯定是最后一个点,所以求得的总点数-1 = 乌龟的点序号。借助npoints()可以返回当前几何体的点数。
    乌龟吃鱼需要用到for循环,循环的次数为鱼的个数,也就是总点数-1 。每次移动后用乌龟的坐标和每一条鱼的坐标去对比,如果相同则鱼被吃掉同时乌龟体力值+0.2。
    读取乌龟和小鱼的点坐标,可以借助point()。


    point
    借助setpointattrib()来修改乌龟的pscale。
    setpointattrib
    借助removepoint()来剔除被吃掉的点。
    removepoint
    注:这两个函数内的point_num都可以用循环中的指针i来引导。

    结构如下

    bite.png

    代码如下

    int npt = npoints(0);
    
    vector turtleP = point(0,'P', npt-1);
    for( int i=0; i<npt-1; i++){
        if( point(0, 'P', i) == turtleP){
        float npscale = point(0, 'pscale', npt-1) + 0.2;
        setpointattrib(0, 'pscale', npt-1, npscale);
        removepoint(0, i);
        }
    }
    

    至此主要程序架构就结束了,通过copy to point 选择不同的组来进行几何体复制,这里用了Houdini自带的乌贼和海龟玩具。


    copytopoint选项注意勾选pack and instance

    注:

    勾选pack and instance是为了让几何体以内存指针形式存在,从而不需要复制详细信息转而以一个点来取代,也就是之前我们写入的小鱼和乌龟的点。
    同时要检查geometry spreadsheet中点的坐标是否正确,如果和输入点的数值有差别,通常是因为没有将Pivot Location 改为Origin。

    最终完成的乌龟吃鱼动图


    如果深入还可以从以下几点入手:
    1.几何体的移动方向伴随转动;
    2.几何体移动从跳动变为线性移动;

    有时间再回来迭代吧!先睡了!

    Cheers !

    相关文章

      网友评论

          本文标题:Houdini日记 | 闲得蛋疼之可视化小甲鱼吃鱼小游戏

          本文链接:https://www.haomeiwen.com/subject/klmujktx.html