Category Archives: Hello World

Hello World

cocos刚体子节点不随父节点移动的坑

早听说cocos的坑挺多~~~坑就在那里,只要坚持搞,早晚会碰上O(∩_∩)O~
昨天碰到了这样一个问题:触屏滑动,父节点旋转角度,子节点移动位置。本来移动子节点的移动效果已经做好了。然后给子节点加了刚体组件,准备处理后续碰撞逻辑。可是加了刚体以后,子节点不再随父节点旋转了。。。
刚才搜了一下解决方案,syncRotation/syncPosition,加上以后,问题解决,具体用法如下:
this.node.getComponent(cc.RigidBody).syncRotation(true)
———
这应该不是cocos的bug,而是,加了刚体以后,运动的处理逻辑交给物理引擎处理,所以不再跟随父节点。。。也么说也能解释的通~~~好歹人家提供了解决方法^_^

Hello World

条件语句的执行顺序

刚刚有碰上了一个python的无厘头问题:
if len(arr) >= 3 & len(arr[2]) > 0: xxx
arr是list,长度可能是2,也可能是3。上面这条语句,按理说正常语言都没问题,程序会在第一个条件为true以后,再去判断第二个,所以不会出现数组越界
maxos里,python3.7执行正常
到了linux里,python3.4,居然报了list越界错误。。。
。。。好吧,改改写法:
if len(arr) >= 3: if len(arr[2]) > 0: xxx
拆成倆if,执行通过
果然真的是先去执行第二个判断条件。。。

Hello World

dataframe根据给定list排序

pandas自带的sort_values已经算是足够强大了,能够满足大部分需求,但是偶然,会有定制排序的需求,比如在输出数据表格的时候,想要把重点项目放在最前面。下面的方法,介绍了一种排序思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
#构建测试数据
df1 = pd.DataFrame({
	'uid': ['小A',  		'小B',  		'小A',  		'小A',  		'小A',  		'小B',  		  '小B',  	     '小A',  		'小B',  	   '小A'],
	'day': ['2021-09-25',  	'2021-09-25',  '2021-09-25',  	'2021-09-26',  '2021-09-26',  '2021-09-26',  	'2021-09-26', '2021-09-27',  '2021-09-25',  '2021-09-25'],
	'sub': ['语文', 		'语文', 		'数学', 		'语文', 		 '数学', 		  '语文',           '数学',        '语文',         '英语',        '英语'],
	'val': [95,             95,            74,             87,             65,             77,              66,            32 ,           55,            90]}, 
		columns=['uid', 'day', 'sub', 'val'])
sortList = ['数学', '语文', '英语']  #排序依据数组,依据sub,科目
# df1.reset_index(inplace=True) #视数据实际情况,排序字段不能是索引
df1['sub'] = df1['sub'].astype('category') #重新设置字段类型,为排序做准备
df1['sub'].cat.set_categories(sortList, inplace=True) #设置排序依据
df1.sort_values('sub', inplace=True) #排序操作
print(df1)

输出:
———–
uid day sub val
2 小A 2021-09-25 数学 74
4 小A 2021-09-26 数学 65
6 小B 2021-09-26 数学 66
0 小A 2021-09-25 语文 95
1 小B 2021-09-25 语文 95
3 小A 2021-09-26 语文 87
5 小B 2021-09-26 语文 77
7 小A 2021-09-27 语文 32
8 小B 2021-09-25 英语 55
9 小A 2021-09-25 英语 90
———–
如上,既完成了根据sortList预定义顺序的排序操作
不过有个地方需要注意:如果sub列里包含sortList没有的值,这里的输出结果可能会出现未知异常
——
over
转载请注明出处: http://www.jiangkl.com/2021/10/dataframe_sort_list

Hello World

dataframe快速转dict

习惯了新瓶装旧酒,pandas各种数据结构虽然很好用,可以有时候还是喜欢熟悉的json。不过python自带的dataframe转json方法并不好用,比如datetime格式就会转不过去。可实际上,大部分时间不用管这个点,因为转json,只是为了打到日志里,看着方便。。。所以,转dict就可以。这里就用到了panda自带的to_dict()方法
比如有下面这样一个dataframe,df1:

现在想将每个城市的平均气温打印出来,就可以这么操作:
str(df1.set_index(‘城市’)[‘平均温度’].to_dict())
输出:{“平均温度”:{“北京”:19, “上海”:23, “深圳”:29}}
——
over

Hello World

php is_numeric 的使用限制

项目里有个地方需要拼insert语句,对于字段值,非数字类型需要加引号,图省事、通用,直接用is_numeric做了判断,上线运行一段时间,还挺顺利
不过今天突然发现,项目出错了,mysql insert执行报错
“Invalid datetime format: 1367 Illegal double ‘523e09488200002’ value found during parsing”
于是缩短insert条数、以便日志打印出整个出错sql,果然,’523e09488200002’没有被当成字符串处理,没有加引号
简单测试发现,果然,is_numeric(‘523e09488200002’),返回是true 咕~~(╯﹏╰)b

第一反应:php的bug,还是被当成16进制了?
于是多试了几个类似的字符串
———–
523a09488200002: 字符串
523b09488200002: 字符串
523e09488200002: 数字
523f09488200002: 字符串
524e094213214: 数字
523e59: 数字
234e234: 数字
e: 字符串
2e5: 数字
12e: 字符串
—————-
只有“e”被特殊照顾。。。顿悟:科学计数法
进一步查了一下is_numeric的使用限制,不仅科学计数法,前置“0x”、中置“.”、前置“-”,都会被当成数字,返回true
所以这里再用is_numeric的话,就要注意了,要再加一层判断逻辑

———-
转载请注明出处:http://www.jiangkl.com/2021/08/php-is_numeric-bug

Hello World

使用pandas+openpyxl生成多子表excel

虽然python+pandas自身有强大的数据分析和展现能力,可实际工作中,不是每个人都会coding的,所以,有时候把粗加工的数据,以excel的形式提供数据,会更灵活、更适合大部分,这里介绍一种可以生成多子表excel的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import pandas as pd
from openpyxl import Workbook
from openpyxl.reader.excel import load_workbook
 
#数据暂存容器
xlsDatas = {}
#保存路径
savePath = '.../test-excel.xlsx'
#获取数据
df1 = pd.DataFrame(
    {
        '日期': ['2021-01-01', '2021-01-02', '2021-01-03', '2021-01-04'], 
        '订单数': [1234, 1321, 3456, 6543],
        '用户数': [234, 787, 987, 545]},
    columns=['日期', '订单数', '用户数']
)
xlsDatas['概况'] = df1  #子表名-子表数据
df2 = pd.DataFrame(
    [
        [56335, '兰州拉面', 15.0, '2021-01-01 12:12:12'], 
        [45135, '山西削面', 13.0, '2021-01-01 12:16:12'], 
        [16335, '兰州拉面', 15.0, '2021-01-01 12:32:12']
    ],
    index=list('123'),
    columns=['用户id', '产品名称', '消费金额', '消费日期']
)
xlsDatas['订单列表'] = df2
#使用Workbook保存数据
book0 = Workbook()
book0.save(savePath) #先保存一个空excel文件,然后往里塞子表,实际会有一个空的Sheet子表
book = load_workbook(savePath)
writer = pd.ExcelWriter(savePath, engine='openpyxl') #选择openpyxl引擎
writer.book = book
for k in xlsDatas :
    xlsDatas[k].to_excel(writer, sheet_name=k)
writer.save()

————
over
转载请注明出处:http://www.jiangkl.com/2021/08/pandas_openpyxl_excel

Hello World

python数字数组的join操作

从其他弱类型语言转到python,最大的感受就是,时不时会有这种感慨:
“卧槽,这样也行”
“卧槽,这也不行”
比如,数组的差值操作,可以直接这么取:
listDiff = list1 – list2
再比如,刚刚碰到的这个,数组的join操作
uIdArr是数字数组,要拼到sql里做查询
“,”.join(uIdArr)
这样?
会报错。。。很正常的一个操作,居然过不去。。。
需要吧userIdArr变成字符串数组
~~好吧
那么,开个for循环?
for当日可以,但是对于python这种追求简单的语言,必然有更简洁的方式:
“,”.join([str[uid] for uid in uIdArr])
——-
over

Hello World

apply与lambda简单搭配,一行代码实现按条件补齐数据

需求:订单列表里的点位信息,只有点位编号,没有点位场景类型,需要补齐这个数据,以便实现按场景分析订单数据

1
2
3
4
5
  orderDf = pd.read_csv('path...')  #订单数据
  nodePlaces = ... #查库,返回 点位编号 - 场景类型 字典数据
  orderDf['place'] = orderDf.apply(lambda x: nodePlaces[x.nodeId] if x.nodeId in nodePlaces else 0, axis = 1) //#补齐场景类型字段place
  print(orderDf.colums)  #查库补齐效果
  print(orderDf.tail(5))

实际测试,orderDf七千万行,nodePlaces五万个点位,添加place这一行,执行时间约为1分钟
over
—–
转载请注明出处:http://www.jiangkl.com/2021/07/pandas_apply_lambda
—–
两周后补充:
这个需求,还有一个效率更高的方法,就是使用pd.merge()
已上面的例子继续折腾:

1
2
3
4
5
6
  orderDf = pd.read_csv('path...')  #订单数据
  nodePlaces = ... #查库,返回 点位编号 - 场景类型 字典数据
  nodeDf = pd.DataFrame(list(nodePlaces()), columns=['nodeId', 'place']) #转成datafram
  orderDf = pd.merge(orderDf, nodeDf, on='nodeId', how='left') //#补齐场景类型字段place
  print(orderDf.colums)  #查库补齐效果
  print(orderDf.tail(5))

我没实际在使用上面的例子做测试,而是再另一个场景下使用了这种方法,实际对比,原来apply使用200秒的一个需求,换成这里的merge以后,降到了700毫秒,降了三个数量级!
不过这个方法也有个缺陷,那就是,如果nodePlaces里没有这个nodeId,orderDf的place字段,会被设置为NaN,解决的办法有两个:
1. 使用fillna替换
2. 构建nodeDf的时候,将nodePlaces里没有的nodeId也补进去

Hello World

Untiy笔记–鼠标跟随移动

这里的使用场景是飞机的移动,飞机跟随鼠标移动,但飞机会有移动速度上限.
实际使用时,最大速度bigSpeed的数值可以通过unity开发工具设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    public float bigSpeed; //每毫秒最大的移动距离
    private Transform fighterTransform; //游戏对象
    private long lastUpdate; //记录最近一次update的时间
 
    private void Update(){
        //因为仅需要每次移动时刷新位置,所以不需要放在FixedUpdate里
        FollowMouseMove();
    }
 
    private void FollowMouseMove(){
        long now = (System.DateTime.Now.Ticks - 621355968000000000)/10000;
        //首先获取飞机当前位置
        Vector3 fightPos = fighterTransform.position;
        //鼠标当前位置,ScreenToWorldPoint用于转化为游戏内坐标体系
        Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        //2D游戏,抛去z轴信息
        mousePos = new Vector3(mousePos.x, mousePos.y, 0f);
        //飞机鼠标位置的距离
        float distance = Vector3.Distance(fightPos-fix, mousePos);
        //两帧之间最大移动距离,bigSpeed是每毫秒的最大移动距离 Update的调用时间并不准确,为了速度均衡,这里根据时间差值来计算
        float maxLen = ((float)(now - lastUpdate))*bigSpeed;
        Vector3 targetPos; //飞机的目标位置
        //飞机向鼠标位置移动  未超过最大距离
        if(distance <= maxLen){
            targetPos = mousePos;
        }else{
            //三角计算
            float dx = (maxLen*(mousePos.x-fightPos.x))/distance;
            float dy = (maxLen*(mousePos.y-fightPos.y))/distance;
            Vector3 dpos = new Vector3(dx, dy, 0f);
            targetPos = fighterTransform.position + dpos;
        }
        //TODO:targetPos是否超出屏幕范围的判断
        fighterTransform.position = targetPos; //将目标位置复制的游戏对象
        lastUpdate = now;
    }

充分使用Vector3自带运算,对计算过程做精简

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    private void FollowMouseMove(){
        long now = (System.DateTime.Now.Ticks - 621355968000000000)/10000;
        Vector3 fightPos = fighterTransform.position;
        Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);  //WorldToScreenPoint
        mousePos = new Vector3(mousePos.x, mousePos.y, 0f);
        float distance = Vector3.Distance(fightPos, mousePos); //当前距离
        float rank;
        float maxLen = ((float)(now - lastUpdate))*bigSpeed; //实际可以移动的最大距离
        if(distance < maxLen){
            rank = 1;
        }else{
            rank = maxLen/distance;
        }
        Vector3 dpos = (mousePos - fightPos)*rank;
        Vector3 targetPos = fighterTransform.position + dpos;
        //CanNotOUtCamera 出界判断模块
        targetPos = CanNotOUtCamera(targetPos, fighterTransform.localScale);
        fighterTransform.position = targetPos;
        lastUpdate = now;
    }
Hello World

Unity笔记–利用预制体动态创建对象

使用Unity制作射击游戏,屏幕内的每一个子弹都需要一个游戏对象,如果是单发武器还好,项目里复制几个对象,然后下面的代码就能不断循环使用。

1
2
    transform.SetAsLastSibling();
    bulletsTransform = transform.GetChild(0);

但如果需要使用连发武器,比如加特林之类的,同一时间屏幕上可能会出现许多的子弹。如果还是事先复制一堆子弹对象,就显得特别low,这里就需要动态创建子弹对象了。
首先,新建预制体子弹对象“Bullet1”,然后将其拖入Assets/Resources目录内(只有名为“Resources”目录内的资源才能动态加载)。
然后就可以在代码内创建、使用预制体对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
    //代码片段1,加载预制体, 创建对象
    GameObject bullet = Resources.Load("Bullet1") as GameObject; //加载预制体
    bullet = Instantiate(bullet); //使用这种方式加载预制体,必须序列化后才能使用
    bullet.transform.parent = cannonsTransform; //设置父级对象
    bullet.transform.localScale = bulltScale; //设置大小。。。预制体似乎为包含拖到修改大小的信息,这里使用中一个Vector3对象重新设置其大小
    bullet.transform.position = beginPos; //使用一个Vector3对象,设置初始位置
    runingBullets.Add(bullet.transform); //加入到队列
 
    //代码片段2,销毁预制体
    if(...){
        Destroy(runingBullets[i].gameObject); //销毁对象
        runingBullets.RemoveAt(i); //移出队列
    }

再unity中,创建对象是很耗资源的操作,正式项目建议创建后不要轻易销毁,而是通过SetActive(false/true)循环使用。
——-
原创内容,转载请注明出处:http://www.jiangkl.com/2021/04/unity_new_prefabs