美文网首页
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日记 | 闲得蛋疼之可视化小甲鱼吃鱼小游戏

    前天晚上睡前突然想起了当初接触Python时的灵魂导师小甲鱼,然后想到了他的一道经典课后题。 游戏编程: 按以下要...

  • 闲得蛋疼

    这有可能是我写的最后一篇《环清笔潭》!三年了,目睹神州大地上的诸多怪事,沙雅达人赶脚不吐不快! 所以就唠叨了一番。...

  • 闲得蛋疼

    我没蛋蛋,蛋疼只是我的一个比喻和想象,但此时此刻我似乎感受到了蛋疼的痛感! 眼看交作业的时间到了,可还没有要写的意...

  • 闲得蛋疼

    最近一段时间给自己安排了比较多的事情,本以为因为忙碌,会容易变得焦躁,但这些日子过下来发现疲倦是有的,但焦躁却...

  • 闲得蛋疼

    技术为人有何罪, 纯属作贱却生悲。 莫道爱树爱某某, 人心向善无可非。

  • 闲得蛋疼

    手机上冷不丁看到一个冷笑话:“女孩择偶时大多会很纠结:该选择一个对自己很好却没有钱的穷光蛋呢,还是该答应另一个对自...

  • 冬日杂谈

    这不是一个闲得蛋疼的季节,但又总想写点闲得蛋疼的文字。聊以自慰也好,自我闲谴也罢。 人们都说“男人...

  • 闲得蛋疼的老王

    老王是我们的领导,二把手那种,之前跑销售出身。老王虽然没什么学历,但是江湖跑久了,人情世故特别在行。喝酒抽烟讲黄段...

  • 闲得蛋疼的胡诌

    每天都重复着太阳的东升与西落,繁华街头的喧闹与寂静,城市道路的拥挤与空荡。 人们过惯了这种反复式的生活,基于快节奏...

  • 在Ubuntu下作妖之闲的蛋疼篇

    你闲的蛋疼么? 你闲的蛋疼么? 你闲的蛋疼么? 你肯定闲的蛋疼,要不然也不会看这篇文章~~ 好吧,想好好的理解这篇...

网友评论

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

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