代码初始化

This commit is contained in:
Giao committed 2026-03-21 17:10:22 +08:00
1 parent 8160eca074
commit dffebce01d
43 files changed
+6383

No files matched your search

@@ -0,0 +1,154 @@
# coding=utf-8
from ..ToolsConfig import *
from BlueFoxSystem import BlueFoxServerSystem
from ..OtherTools import Mount
from LocalCoord import FixPosition, OriginCoord, RotCoord, RotAxes, BlockFaceRot
blockBlocks = {}
blockEntityDataComp = serverCompFactory.CreateBlockEntityData(LevelId)
def GetLoadedBlockBlockByPosDim(posDim):
# type:(Tuple[int,int,int,int]) -> BlockBlock
"""
通过维度坐标获取已经加载的方块块
"""
return blockBlocks.get(posDim)
def GetLoadedBlockBlock():
# type:() -> List[BlockBlock]
"""
获取所有已加载的方块块
"""
return list(set(blockBlocks.values()))
@Mount(BlueFoxServerSystem)
class BlockBlock(object):
"""
方块块,用于解决模型体积过大导致的模型消失的问题
"""
blueFoxSystem = BlueFoxServerSystem if 0 else None
blockList = {}
@classmethod
def Load(cls, posDim):
# type:(Tuple[int,int,int,int]) -> None
"""
通过维度坐标加载储存的块数据
"""
data = blockEntityDataComp.GetBlockEntityData(posDim[3], posDim[:3])
if data and data['isCenter']:
posDims = [tuple(_) for _ in data['posDims']]
return cls(tuple(data['centerPosDim']), data['aux'], posDims)
def __init__(self, posDim, aux, posDims=None):
# type:(Tuple[int,int,int,int],int,List[Tuple]) -> None
self.blockPosDims = posDims # 方块块控制坐标
self.centerPosDim = posDim # 控制中心
self.aux = aux
if self.blockPosDims is None:
self.blockPosDims = self.CreatePosDims(posDim, aux)
self.CreateBlocks()
self.dataComp = blockEntityDataComp.GetBlockEntityData(self.centerPosDim[3], self.centerPosDim[:3])
self.dataComp['posDims'] = self.blockPosDims
self.dataComp['aux'] = self.aux
for posDim in self.blockPosDims:
blockBlocks[posDim] = self
def ReplaceBlocks(self, newBlockList, otherData):
"""
替换方块,新方块依然具有方块实体的要求
"""
oldData = {
'posDims': self.blockPosDims,
'aux': self.aux
}
oldData.update(otherData)
self.DestroyBlocks()
self.CreateBlocks(newBlockList)
self.ResetBlockData(oldData)
def ResetBlockData(self, data):
for K_V in data.items():
self.dataComp[K_V[0]] = K_V[1]
def DestroyFunction(self):
"""
销毁处理逻辑不清除方块
"""
for posDim in self.blockPosDims:
del blockBlocks[posDim]
def Destroy(self):
"""
销毁方块块
"""
self.DestroyFunction()
self.DestroyBlocks()
def DestroyBlocks(self):
"""
销毁方块块所控制的方块
"""
blockComp = serverCompFactory.CreateBlockInfo(LevelId)
for posDim in self.blockPosDims:
blockComp.SetBlockNew(posDim[:3], blockDict={'name': 'minecraft:air'}, dimensionId=posDim[3])
def CreateBlocks(self, blockList=None):
"""
放置方块块所控制的方块
"""
if blockList is None:
blockList = self.blockList
blockComp = serverCompFactory.CreateBlockInfo(LevelId)
for pos in self.blockList:
blockInfo = blockList[pos]
blockInfo['aux'] = self.aux
posDim = self.CalculatePosDimFromOffset(pos, self.centerPosDim, self.aux)
blockComp.SetBlockNew(posDim[:3], blockDict=blockInfo, dimensionId=posDim[3])
dataComp = blockEntityDataComp.GetBlockEntityData(posDim[3], posDim[:3])
dataComp['isCenter'] = True if not cmp(pos, (0, 0, 0)) else False
dataComp['centerPosDim'] = self.centerPosDim
self.dataComp = serverCompFactory.CreateBlockEntityData(LevelId).GetBlockEntityData(
self.centerPosDim[3], self.centerPosDim[:3]
)
def IsSelf(self, posDim):
"""
判断一个维度坐标是否为本方块块
"""
for posDimSelf in self.blockPosDims:
if not cmp(posDimSelf, posDim):
return True
return False
@classmethod
def CreatePosDims(cls, posDim, aux):
"""
创建方块块控制坐标
"""
return [cls.CalculatePosDimFromOffset(pos, posDim, aux) for pos in cls.blockList]
@classmethod
def CalculatePosDimFromOffset(cls, offset, posDim, aux):
originCoord = OriginCoord(posDim)
_, __, ___ = FixPosition(originCoord.AbsPos(
RotCoord(BlockFaceRot[aux], RotAxes.Y).CalculatePos(offset)
))
return _, __, ___, posDim[3]
@classmethod
def Able(cls, posDim, aux):
"""
判断是否可以生成块
"""
blockComp = serverCompFactory.CreateBlockInfo(LevelId)
for posDim in cls.CreatePosDims(posDim, aux):
block = blockComp.GetBlockNew(posDim[:3], posDim[3])
if block is None or (block and block['name'] != 'minecraft:air'):
return False
return True
@@ -0,0 +1,118 @@
# coding=utf-8
from math import *
from ..ToolsConfig import *
import time
class GraduallySum(object):
"""
渐变数
"""
def __init__(self, coefficient, target, initValue):
self.target = target
self.coefficient = coefficient
self.value = initValue
def ResetCoefficient(self, newCoefficient):
self.coefficient = newCoefficient
def Refresh(self):
self.value += self.coefficient * (self.target - self.value)
def ResetTarget(self, newTarget):
self.target = newTarget
class GraduallySumTime(GraduallySum):
"""
时间相关渐变数
"""
def __init__(self, coefficient, target, initSum):
self.time = time.time()
super(GraduallySumTime, self).__init__(coefficient, target, initSum)
def Refresh(self):
_time = time.time()
coe = self.coefficient * (_time - self.time)
# 放置系数过大
coe = coe if coe < 0.99 else 0.99
self.value += coe * (self.target - self.value)
def Clamp(value, _min, _max):
# type:(float,float,float) -> float
return _max if value > _max else _min if value < _min else value
def ComBin(i, place, result):
# type:(int,int,int) -> bool
"""
提取参数i位于place的结果并于result比较
例如输入:0b00101,0b10000,0b01110,则会返回false
"""
return i & place == result & place
def BinCal(i, place, result):
# type:(int,int,bool) -> int
"""
为参数i设置位于place的result结果
例如输入:0b00100,0b00101,True
输出将会是:0b00101
"""
return ((i | place) ^ place) | place * result
def CosOfTwoVec(vec1, vec2):
"""
获取两个相同维度向量的夹角cos值
"""
if vec1.__len__() != vec2.__len__():
return 0
nv1 = Normalize(vec1)
nv2 = Normalize(vec2)
_ = 0
for index in range(nv1.__len__()):
_ += nv1[index] * nv2[index]
return _
def Normalize(vec):
# type:(Union[List,Tuple]) -> Union[List,Tuple]
"""
将任意维度向量转换为单位向量
"""
r = 0
for _ in vec:
r += _ ** 2.0
r = r ** 0.5
cls = vec.__class__
return cls([(_ / r) for _ in vec])
def GetTwoPointVec(pos1, pos2):
# type:(Union[List,Tuple],Union[List,Tuple]) -> Tuple
"""
计算任意两点的方向向量, 非单位向量, pos1 -> pos2
"""
li = []
if pos1.__len__() != pos2.__len__():
return tuple(li)
for index in range(pos1.__len__()):
li.append(pos2[index] - pos1[index])
return tuple(li)
def MatProduct(vec, mat):
"""
计算向量或者坐标与矩阵相乘, 需要i=j
"""
new = [0.0 for _ in range(vec.__len__())]
lengthIndex = range(vec.__len__())
for _ in lengthIndex:
for __ in lengthIndex:
new[_] += vec[__] * mat[_][__]
return new
@@ -0,0 +1,657 @@
# coding=utf-8
from ..ToolsConfig import *
from ..ItemTools import IsSameItem, GetItemBaseInfoSafely, GetItemDurabilityPercent, DeclareItemAnyNum, IsItemDict
from ..ScreenTools import ScreenTools
from BlueFoxSystem import BlueFoxClientSystem
from ..OtherTools import Mount, FixList
from ControllableUi.Animation import MoveToPos
class SlotOffset(dict):
def __getitem__(self, item):
return super(SlotOffset, self).get(item, 0)
@Mount(BlueFoxClientSystem)
class RucksackScreen(baseScreen):
flyingItemRender = None
slotOffset = SlotOffset()
flyingMoveControl = None # type:MoveToPos
blueFoxClient = BlueFoxClientSystem if 0 else None
def __init__(self, name_spase, ui_key, param, items=None, customData=None, flyingPanelPath=None):
super(RucksackScreen, self).__init__(name_spase, ui_key, param)
self._selectSlot = -1
self.__itemNum = [] # 物品数量
self.__itemD = [] # (目前,总) 耐久度
self.__itemId = [] # 物品ID
self.__isItemNone = [] # 是否为缺失物品
self.flyingItem = {} # 跟随鼠标的物品
self.moveControls = []
self.exchangeLock = False
self.items = items # 所有物品
if self.items is None: self.items = [] # type:List[Dict]
self.customData = customData # 自定义数据
self.gridIndex = {}
self.flyingPanelPath = flyingPanelPath
self.offsetSlot = {}
self._selectSlot = -1
@property
def selectSlot(self):
return self._selectSlot
@selectSlot.setter
def selectSlot(self, index):
self._selectSlot = index
def GetStartAndFinalIndex(self, collectName):
"""
获取起止索引段
:param collectName:
:return:
"""
offsetKey = self.offsetSlot.keys()
offsetKey.sort()
offset = self.slotOffset[collectName]
if offset == offsetKey[-1]: return offset, len(self.items)
for index in range(len(offsetKey) - 1):
if offset == offsetKey[index]: return offset, offsetKey[index + 1]
def RecalculateOffsetSlot(self):
"""
重新计算OffsetSlot
:return:
"""
self.offsetSlot = {}
for _ in self.slotOffset.items(): self.offsetSlot[_[1]] = _[0]
def PrintSlotName(self):
offsetKey = self.offsetSlot.keys()
offsetKey.sort()
for _ in offsetKey:
print (_, self.offsetSlot[_])
print '--------------'
def AddSlot(self, slotName, gridPath, items, dim, refresh=True):
"""
增加槽位,需要在Create函数执行之前绑定所需的connectionName
:param slotName: 槽位名称
:param refresh: 是否刷新数据
:param gridPath: 网格路径
:param items: 物品集
:param dim: 网格尺寸
:return:
"""
if slotName in self.slotOffset: return
self.InitSlot(slotName, gridPath, items, dim)
self.InitItemData() if refresh else 0
def DelSlot(self, slotName, refresh=True):
"""
删除槽位,包括items,slotOffset,offsetSlot
:param slotName:槽位名称
:param refresh: 是否刷新数据
:return:
"""
if not slotName in self.slotOffset: return
offsetKey = self.offsetSlot.keys()
offsetKey.sort()
startAndEndIndex = self.GetStartAndFinalIndex(slotName)
oldOffset = startAndEndIndex[0]
size = startAndEndIndex[1] - startAndEndIndex[0]
del self.items[startAndEndIndex[0]: startAndEndIndex[1]]
del self.offsetSlot[oldOffset]
del self.slotOffset[slotName]
del self.gridIndex[slotName]
for index in offsetKey[::-1]:
if oldOffset < index: self.slotOffset[self.offsetSlot[index]] = index - size
self.RecalculateOffsetSlot()
self.InitItemData() if refresh else 0
# ------------------------------ init ------------------------------
def InitItemDataOne(self, slot, slotName, item):
"""
对某个槽位进行初始化,需要在InitData之后调用
"""
if item:
itemInfo = GetItemBaseInfoSafely(item)
self.__itemNum[slot] = item['count']
self.__itemId[slot] = itemInfo['id_aux']
self.__itemD[slot] = GetItemDurabilityPercent(item, itemInfo)
self.__isItemNone[slot] = False
else:
self.__itemNum[slot] = 0
self.__itemD[slot] = (0.0, 0.0)
self.__itemId[slot] = 0
self.__isItemNone[slot] = False
def InitItemData(self):
"""
依据当前items初始化显示所需的物品数据
:return:
"""
self.moveControls = [None for _ in self.items]
self.__itemNum = [0 for _ in self.items]
self.__itemD = [(0.0, 0.0) for _ in self.items]
self.__itemId = [0 for _ in self.items]
self.__isItemNone = [False for _ in self.items]
offsets = self.offsetSlot.keys()
offsets.sort()
for offsetIndex in range(len(offsets[:-1])):
for index in range(offsets[offsetIndex], offsets[offsetIndex + 1]):
if not IsItemDict(self.items[index]): self.items[index] = {}
self.InitItemDataOne(index, self.offsetSlot[offsets[offsetIndex]], self.items[index])
for index in range(offsets[-1], len(self.items)):
if not IsItemDict(self.items[index]): self.items[index] = {}
self.InitItemDataOne(index, self.offsetSlot[offsets[-1]], self.items[index])
def InitFlyingPanel(self, path):
self.flyingItemRender = self.GetBaseUIControl(path).asItemRenderer()
self.flyingMoveControl = MoveToPos(self.flyingItemRender, 0.1)
def InitSlot(self, slotName, gridPath, items, dimension):
"""
初始化物品槽
:param dimension: 物品槽网格的尺寸
:param items: 物品槽内的物品
:param slotName:物品槽名称
:param gridPath:物品槽的网格路径
:return:
"""
items = FixList(items, dimension[0] * dimension[1], {})
self.GetBaseUIControl(gridPath).asGrid().SetGridDimension(tuple(dimension))
self.gridIndex[slotName] = gridPath
offset = len(self.items)
self.slotOffset[slotName] = offset
self.offsetSlot[offset] = slotName
self.items += items
self.BindingAll(slotName)
# ------------------------------ init ------------------------------
def PlayerInvItemChanged(self, args):
"""
玩家背包服务端主动发生改变时触发
"""
self.SetUiVisibleInfo(args['item'], args['slot'])
def Create(self):
self.blueFoxClient.ListenForEvent(
MOD_NAME, 'BlueFoxServerSystem', 'PlayerInvItemChanged', self, self.PlayerInvItemChanged
)
def Destroy(self):
self.blueFoxClient.UnListenForEvent(
MOD_NAME, 'BlueFoxServerSystem', 'PlayerInvItemChanged', self, self.PlayerInvItemChanged
)
playerId = clientApi.GetLocalPlayerId()
x, y, z = clientCompFactory.CreatePos(playerId).GetFootPos()
rot = clientCompFactory.CreateRot(playerId).GetRot()
rx, ry, rz = clientApi.GetDirFromRot(rot)
self.blueFoxClient.NotifyToServer('RucksackScreenClosed', {
'posDim': (
(x + rx * 0.3, y + 1.08, z + rz * 0.3), clientCompFactory.CreateGame(LevelId).GetCurrentDimension()
), 'playerRot': rot
})
for moveControl in self.moveControls: moveControl.Finish() if moveControl else 0
def ItemCellGridInit(self, gridPath, collectionName):
"""
物品槽网格初始化时触发
:param gridPath:网格路径
:param collectionName:集合名称
:return:
"""
pass
def BindingAll(self, collectionName):
bindingFunc = ScreenTools.CollectionViewBinder
bindingBool = viewBinder.BF_BindBool
bindingInt = viewBinder.BF_BindInt
bindingFloat = viewBinder.BF_BindFloat
bindingString = viewBinder.BF_BindString
bindingFunc(bindingBool, collectionName, '#item_num_visible', self.ItemsNumVisible) # 是否显示物品数量文字
bindingFunc(bindingString, collectionName, '#item_num', self.ItemsNum) # 显示物品数量
bindingFunc(bindingBool, collectionName, '#select_visible', self.SelectVisible) # 是否显示物品槽选择
bindingFunc(bindingInt, collectionName, '#item_id_aux', self.RenderItems) # 显示物品
bindingFunc(bindingBool, collectionName, '#item_durability_visible', self.ItemsDurabilityVisible) # 是否显示物品耐久度条
bindingFunc(
bindingFloat, collectionName, '#item_durability_total_amount', self.ItemsDurabilityTotal
) # 物品耐久度条总量
bindingFunc(
bindingFloat, collectionName, '#item_durability_current_amount', self.ItemsDurabilityCurrent
) # 物品耐久度条当前量
bindingFunc(bindingBool, collectionName, '#item_none_visible', self.ItemsNone) # 是否显示缺失物品背景
bindingFunc(bindingBool, collectionName, '#item_cell_init', self.ItemCellInit) # 初始化时触发
# --------------------- bindings ---------------------
def GetGirdPath(self, collectionName):
return self.gridIndex[collectionName]
def ItemCellInit(self, collectionName, index):
"""
初始化时触发,无需任何返回值
:param collectionName:集合名称
:param index:在网格内的索引值
:return:
"""
if not index: self.ItemCellGridInit(self.GetGirdPath(collectionName), collectionName)
def ItemsNumVisible(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return self.__itemNum[index] > 1 if index < self.__itemNum.__len__() else False
def ItemsNum(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return str(self.__itemNum[index]) if index < self.__itemNum.__len__() else ''
def SelectVisible(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return index == self.selectSlot
def RenderItems(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return self.__itemId[index] if index < self.__itemId.__len__() else 0
def ItemsDurabilityVisible(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
if index >= self.__itemD.__len__():
return False
info = self.__itemD[index]
return info[0] != info[1] and info[1] > 0
def ItemsDurabilityTotal(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return self.__itemD[index][1] if index < self.__itemD.__len__() else 0.0
def ItemsDurabilityCurrent(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return self.__itemD[index][0] if index < self.__itemD.__len__() else 0.0
def ItemsNone(self, collectionName, index):
index = index + self.slotOffset.get(collectionName, 0)
return self.__isItemNone[index] if index < self.__isItemNone.__len__() else False
# --------------------- bindings ---------------------
def SetNoneItem(self, slot, itemDict):
"""
将某个槽位设置为缺少物品
:param slot:
:param itemDict:
:return:
"""
itemInfo = GetItemBaseInfoSafely(itemDict)
self.__itemNum[slot] = itemDict['count']
self.__itemD[slot] = GetItemDurabilityPercent(itemDict, itemInfo)
self.__itemId[slot] = itemInfo['id_aux']
self.__isItemNone[slot] = True
self.items[slot] = {}
def NoticeServerChangeItem(self, slot, itemDict, itemOperate):
self.blueFoxClient.NotifyToServer(
'ChangeItem', {
'slot': slot, 'item': itemDict, 'itemOperate': itemOperate,
'posDim': None, 'posType': self.GetOffsetNameByIndex(slot),
'startSlot': self.GetOffsetStartIndex(slot)
}
)
def NoticeServerSpawnItemEntities(self, itemDict):
playerId = clientApi.GetLocalPlayerId()
x, y, z = clientCompFactory.CreatePos(playerId).GetFootPos()
self.blueFoxClient.NotifyToServer("SpawnItemEntity", {
'posDim': (
(x, y + 1.6, z), clientCompFactory.CreateGame(LevelId).GetCurrentDimension()
), 'item': itemDict,
'playerRot': clientCompFactory.CreateRot(playerId).GetRot()
})
def SetItem(self, slot, itemDict=None, noticeServer=True):
"""
设置某个槽位, 不论是否物品为空, 返回原本物品
:param noticeServer:
:param itemDict:
:param slot:
:return:
"""
return self.SetUiItem(slot, itemDict, noticeServer) if itemDict else self.SetUiItemNone(slot, noticeServer)
def SetItemAbs(self, slot, itemDict=None, noticeServer=False):
"""
设置某个槽位, 不论是否物品为空
:param noticeServer:
:param itemDict:
:param slot:
:return:
"""
self.SetUiItemAbs(slot, itemDict, noticeServer) if itemDict else self.SetUiItemNone(slot, noticeServer)
def SetUiItemNone(self, slot, noticeServer=None):
"""
将某个槽位设置为空,返回原本物品
:param noticeServer:
:param slot:
:return:
"""
originItem = self.items[slot]
self.SetUiItemAbs(slot, {}, noticeServer)
return originItem
def SetUiItemAbs(self, slot, destinationItem, noticeServer=True, itemOperate=None):
"""
设置某一个槽位的物品, 不返回任何物品及合并
:param itemOperate: 物品操作,用于服务端操作
:param noticeServer: 是否通知服务端,默认发送
:param slot:设置槽位
:param destinationItem:目标物品字典
:return:
"""
destinationItemInfo = GetItemBaseInfoSafely(destinationItem)
self.__itemNum[slot] = destinationItem.get('count', 0)
self.__itemD[slot] = GetItemDurabilityPercent(destinationItem, destinationItemInfo)
self.__itemId[slot] = destinationItemInfo.get('id_aux', 0)
self.__isItemNone[slot] = False
self.items[slot] = destinationItem
self.NoticeServerChangeItem(slot, destinationItem, itemOperate) if noticeServer else 0
def SetUiItem(self, slot, destinationItem, noticeServer=True):
"""
设置某一个槽位的物品,返回原本物品如果不可合并或者合并后多出的物品
:param noticeServer: 是否通知服务端
:param slot:设置槽位
:param destinationItem:目标物品字典
:return:
"""
originItem = self.items[slot]
originItemInfo = GetItemBaseInfoSafely(destinationItem)
if IsSameItem(originItem, destinationItem):
if originItem['count'] + destinationItem['count'] <= originItemInfo['maxStackSize']:
destinationItem['count'] += originItem['count']
originItem = {}
else:
originItem['count'] += destinationItem['count'] - originItemInfo['maxStackSize']
destinationItem['count'] = originItemInfo['maxStackSize']
self.SetUiItemAbs(slot, destinationItem, noticeServer)
return originItem
def SetUiVisibleInfo(self, itemDict, slot):
"""
设置物品,通常用于服务端主动设置物品,此接口不会发送ChangeItem事件
:param itemDict:物品信息字典
:param slot:设置槽位
:return:
"""
self.SetUiItemAbs(slot, itemDict, False)
def GetOffsetNameByIndex(self, index):
"""
通过index获取偏移名称
:param index:
:return:
"""
offsetKey = self.offsetSlot.keys()
offsetKey.sort()
offset = offsetKey[-1]
for _ in range(offsetKey.__len__() - 1):
if offsetKey[_] <= index < offsetKey[_ + 1]:
return self.offsetSlot[offsetKey[_]]
return self.offsetSlot[offset]
def GetOffsetStartIndex(self, index):
"""
通过index获取偏移起始index
:param index:
:return:
"""
offsetKey = self.offsetSlot.keys()
offsetKey.sort()
offset = offsetKey[-1]
for _ in range(offsetKey.__len__() - 1):
if offsetKey[_] <= index < offsetKey[_ + 1]:
return offsetKey[_]
return offsetKey[-1]
def GetItemCellByIndex(self, index):
"""
通过槽位获取itemCell的路径
:param index:槽位编号
:return:
"""
offset = self.GetOffsetStartIndex(index)
return self.gridIndex[self.offsetSlot[offset]] + '/item_cell{}'.format(index - offset + 1)
# 以下三个方法是pe背包槽位交互模式
def FlyingMoveFinish(self, slot):
"""
pe下槽位发生物品堆叠时, flyRender动画完成时触发
:return:
"""
self.flyingItemRender.SetVisible(False) if self.flyingItemRender else 0
self.NoticeServerChangeItem(
slot, self.flyingItem,
ItemOperate.ExchangeFlying(
slot - self.GetOffsetStartIndex(slot), self.GetOffsetNameByIndex(slot)
)
)
self.flyingItem = {}
def ExchangeFinish(self, slot, destinationSlot):
"""
pe下槽位发生物品交换时, 动画完成时触发
:param destinationSlot: 与其交换的槽位
:param slot: 槽位
:return:
"""
deSlotName = self.GetOffsetNameByIndex(destinationSlot)
deSlotOffset = destinationSlot - self.GetOffsetStartIndex(destinationSlot)
fromSlotName = self.GetOffsetNameByIndex(slot)
fromSlotOffset = slot - self.GetOffsetStartIndex(slot)
originItem = self.items[slot]
destinationItem = self.items[destinationSlot]
self.SetUiItemAbs(slot, destinationItem, False)
deSlot = self.slotOffset[deSlotName] + deSlotOffset
if deSlotName not in self.slotOffset or deSlot >= len(self.items):
self.flyingItem = originItem
else:
self.SetUiItemAbs(deSlot, originItem, False)
self.NoticeServerChangeItem(slot, {}, ItemOperate.Exchange(
fromSlotOffset, fromSlotName,
deSlotOffset, deSlotName
))
self.flyingItem = {}
def StackFinish(self, slot, destinationSlot):
"""
pe下槽位发生物品堆叠时, slot动画完成时触发
:param slot: 槽位
:param destinationSlot: 与其交换的槽位
:return:
"""
orSlotName = self.GetOffsetNameByIndex(slot)
orSlotOffset = slot - self.GetOffsetStartIndex(slot)
deSlotName = self.GetOffsetNameByIndex(destinationSlot)
deSlotOffset = destinationSlot - self.GetOffsetStartIndex(destinationSlot)
self.flyingItem = self.SetItem(destinationSlot, self.items[slot], False)
orSlot = self.slotOffset[orSlotName] + orSlotOffset
if orSlotName not in self.slotOffset or orSlot >= len(self.items):
self.flyingItem = {}
else:
self.SetItemAbs(slot, self.flyingItem, False)
self.flyingItem = {}
self.flyingItemRender.SetVisible(False, False) if self.flyingItemRender else 0
self.NoticeServerChangeItem(slot, {}, ItemOperate.Merge(
orSlotOffset, orSlotName, deSlotOffset, deSlotName
))
@staticmethod
def SetUiItemForItemRender(render, itemDict):
"""
使用itemDict设置物品渲染
:param render:itemRender
:param itemDict:需要设置的物品字典
:return:
"""
if not itemDict:
render.SetVisible(False)
return
render.SetUiItem(
itemDict.get('newItemName', 'minecraft:air'), itemDict.get('newAuxValue', 0),
(itemDict.get('enchantData', []) + itemDict.get('modEnchantData', [])).__len__() > 0,
itemDict.get('userData', None)
)
def PcDown(self, selectSlot):
"""
电脑点击物品槽触发
:param selectSlot:槽位
:return:
"""
oldItem = self.items[selectSlot]
if self.flyingItem is None: self.flyingItem = {}
self.SetUiItemAbs(
selectSlot, self.flyingItem, True,
ItemOperate.ExchangeFlying(
selectSlot - self.GetOffsetStartIndex(selectSlot), self.GetOffsetNameByIndex(selectSlot)
)
)
self.flyingItem = oldItem
if self.flyingItemRender:
self.SetUiItemForItemRender(
self.flyingItemRender, self.flyingItem
) if self.flyingItem else self.flyingItemRender.SetVisible(False, False)
def GetSlotAnimationState(self, originSlot, destinationSlot):
"""
pe模式下, 交换物品时根据槽位确认动画形式
:param originSlot:先前选择槽位
:param destinationSlot:后选择槽位
:return:先前, 后选择, flyingItem
"""
originItem = self.items[originSlot]
destinationItem = self.items[destinationSlot]
itemInfo = GetItemBaseInfoSafely(originItem)
if IsSameItem(originItem, destinationItem):
if originItem['count'] + destinationItem['count'] < itemInfo['maxStackSize']:
return True, False, False
elif destinationItem['count'] == itemInfo['maxStackSize']:
return True, True, False
else:
return False, False, True
return True, True, False
def GetPeFlyingItem(self, originSlot, destinationSlot):
"""
在pe模式下, 获取flyingItem
:param originSlot: 现物品槽位
:param destinationSlot: 目标物品槽位
:return:
"""
itemDict1, itemDict2 = DeclareItemAnyNum(self.items[originSlot], 64 - self.items[destinationSlot]['count'])
self.SetItemAbs(originSlot, itemDict1)
return itemDict2
def DeliverItem(self, originSlot, destinationSlot):
"""
以动画形式从origin向destination传递物品
:param originSlot:起源物品槽位
:param destinationSlot:目标物品槽位
:return:
"""
ox, oy = self.GetAbsPos(self.GetItemCellByIndex(originSlot) + '/item_cell/item_renderer')
dx, dy = self.GetAbsPos(self.GetItemCellByIndex(destinationSlot) + '/item_cell/item_renderer')
_, __, ___ = self.GetSlotAnimationState(originSlot, destinationSlot)
if _:
self.moveControls[originSlot].StartMove(
(dx - ox, dy - oy), self.ExchangeFinish if __ else self.StackFinish, originSlot, destinationSlot
)
if __:
self.moveControls[destinationSlot].StartMove(
(-dx + ox, -dy + oy), lambda: None
)
if ___:
self.SetUiItemForItemRender(self.flyingItemRender, self.items[originSlot]) if self.flyingItemRender else 0
self.flyingItemRender.SetVisible(True) if self.flyingItemRender else 0
self.flyingMoveControl.ResetInitialPos((ox, oy))
self.flyingMoveControl.StartMove(
(dx, dy), self.StackFinish, originSlot, destinationSlot
)
def PeDown(self, selectSlot):
"""
手机点击物品槽触发
:param selectSlot:槽位
:return:
"""
if self.selectSlot == selectSlot:
self.selectSlot = -1
return
else:
if self.selectSlot is not -1:
if self.items[self.selectSlot]:
self.DeliverItem(self.selectSlot, selectSlot)
self.selectSlot = -1
return
if self.items[selectSlot]:
self.selectSlot = selectSlot
self.flyingItemRender.SetPosition(
self.GetAbsPos(self.GetItemCellByIndex(selectSlot) + '/item_cell/item_renderer')
) if self.flyingItemRender else 0
self.flyingMoveControl.ResetInitialPos()
@viewBinder.binding(viewBinder.BF_ButtonClickUp, '#item_cell_touch')
def ButtonUp(self, args):
"""
物品槽被点击会触发无论电脑或者手机
:param args:与普通的按钮回调参数一致
:return:
"""
touchIndex = args['#collection_index'] + self.slotOffset.get(args['#collection_name'], 0)
self.CreateItemRenderMoveControl(touchIndex)
self.PeDown(touchIndex) if Platform else self.PcDown(touchIndex)
def CreateItemRenderMoveControl(self, slot):
"""
创建物品渲染的移动动画控制器
:param slot:
:return:
"""
if self.moveControls[slot] is None:
itemCell = self.GetBaseUIControl(self.GetItemCellByIndex(slot))
self.moveControls[slot] = MoveToPos(
itemCell.GetChildByName('item_cell').GetChildByName('item_renderer'), 0.1
)
def GetAbsPos(self, path):
"""
获取元素的绝对坐标
:param path:路径
:return:绝对坐标, xy
"""
control = self.GetBaseUIControl(path)
return control.GetGlobalPosition() if control else (0, 0)
@viewBinder.binding(viewBinder.BF_BindInt, '#main.gametick')
def GameTick(self):
if clientCompFactory and not Platform and self.flyingItemRender and self.flyingItem:
x, y = clientCompFactory.CreateActorMotion(clientApi.GetLocalPlayerId()).GetMousePosition()
self.flyingItemRender.SetPosition((x - 9, y - 9))
self.flyingItemRender.SetVisible(self.flyingItem, False)
for moveControl in self.moveControls:
moveControl.Refresh() if moveControl else 0
self.flyingMoveControl.Refresh() if self.flyingMoveControl else 0
def CreateItemEntityIfFlyItem(self):
"""
创建跟随鼠标的物品
:return:
"""
if self.flyingItem: self.NoticeServerSpawnItemEntities(self.flyingItem)
@@ -0,0 +1,117 @@
# coding=utf-8
from Grapher import *
class SizeAnim(MovableUi):
def __init__(self, base_ui_control, time):
# type:(BaseUIControl,float)->None
self.initialSize = base_ui_control.GetSize()
self.totalTime = float(time)
self.backFunc = lambda *args: None
self.args = []
self.fromSize = (0, 0)
self.fatherSize = (0, 0)
self.fromPos = (0, 0)
self.speed = 0
super(SizeAnim, self).__init__(base_ui_control)
def ResetInitialSize(self, initialSize=None):
"""
重置原位置
:return:
"""
self.initialSize = self.baseUiControl.GetSize() if initialSize is None else initialSize
def Reset(self):
super(SizeAnim, self).Reset()
self.baseUiControl.SetSize(self.initialSize)
self.backFunc = lambda: None
self.args = tuple()
def Finish(self):
super(SizeAnim, self).Finish()
self.backFunc(*self.args)
self.Reset()
def StartAnim(self, fromSize, fatherSize, fromPos, backFunc, *backFuncArgs):
self.fromSize = fromSize
self.fatherSize = fatherSize
self.fromPos = fromPos
self.speedX = (fromSize[0] - self.initialSize[0]) / self.totalTime
self.speedY = (fromSize[1] - self.initialSize[1]) / self.totalTime
self.args = backFuncArgs
self.backFunc = backFunc
super(SizeAnim, self).Start()
def Refresh(self):
if self and self.state != ControlState.Run:
return
runTime = self.GetWorkTime()
if runTime < self.totalTime:
size = (
self.fromSize[0] - self.speedX * runTime, self.fromSize[1] - self.speedY * runTime
)
self.baseUiControl.SetSize(size)
position = ((self.fatherSize[0] - size[0]) / 2.0, (self.fatherSize[1] - size[1]) / 2.0)
self.baseUiControl.SetPosition(position)
else:
self.baseUiControl.SetPosition(self.fromPos)
self.baseUiControl.SetSize(self.initialSize)
self.Finish()
return runTime
class MoveToPos(MovableUi):
def __init__(self, base_ui_control, time):
# type:(BaseUIControl,float)->None
self.initialPos = base_ui_control.GetPosition()
self.totalTime = float(time)
self.speedX = 0.0
self.speedY = 0.0
self.destinationPos = (0, 0)
self.backFunc = lambda *args: None
self.args = []
super(MoveToPos, self).__init__(base_ui_control)
def ResetInitialPos(self, initialPos=None):
"""
重置原位置
:return:
"""
self.initialPos = self.baseUiControl.GetPosition() if initialPos is None else initialPos
def Reset(self):
super(MoveToPos, self).Reset()
self.baseUiControl.SetPosition(self.initialPos)
self.backFunc = lambda: None
self.args = tuple()
def Finish(self):
super(MoveToPos, self).Finish()
self.backFunc(*self.args)
self.Reset()
def StartMove(self, destinationPos, backFunc, *backFuncArgs):
self.destinationPos = destinationPos
self.args = backFuncArgs
self.backFunc = backFunc
dx, dy = destinationPos[0] - self.initialPos[0], destinationPos[1] - self.initialPos[1]
self.speedX = dx / self.totalTime
self.speedY = dy / self.totalTime
super(MoveToPos, self).Start()
def Refresh(self):
if self and self.state != ControlState.Run:
return
runTime = self.GetWorkTime()
if runTime < self.totalTime:
self.baseUiControl.SetPosition((
self.speedX * runTime + self.initialPos[0], self.speedY * runTime + self.initialPos[1]
))
else:
self.baseUiControl.SetPosition((
self.destinationPos[0] + self.initialPos[0], self.destinationPos[1] + self.initialPos[1]
))
self.Finish()
return runTime
@@ -0,0 +1,49 @@
# coding=utf-8
from __init__ import *
class MovableUi(Base):
"""
可控的移动性Ui
以当前位置为原点,位置随时间变化
"""
def __init__(self, base_ui_control, moving_route_x=lambda t: 0, moving_route_y=lambda t: 0):
# type:(BaseUIControl,Any,Any)->None
"""
@param base_ui_control: ui控件
@param moving_route_x: x轴的运动路径
@param moving_route_y: y轴的运动路径
"""
self.baseUiControl = base_ui_control
self.movingRouteX = moving_route_x
self.movingRouteY = moving_route_y
self.initialPos = base_ui_control.GetPosition()
Base.__init__(self)
def ResetRouteX(self, routeX=lambda t: 0):
"""
设置为新的X轴运动函数
:param routeX: 函数, 传参运动时间
:return:
"""
self.Reset()
self.movingRouteX = routeX
def ResetRouteY(self, routeY=lambda t: 0):
"""
设置为新的Y轴运动函数
:param routeY: 函数, 传参运动时间
:return:
"""
self.Reset()
self.movingRouteY = routeY
def Refresh(self):
if self.state != ControlState.Run:
return
runTime = self.GetWorkTime()
self.baseUiControl.SetPosition((
self.movingRouteX(runTime) + self.initialPos[0], self.movingRouteY(runTime) + self.initialPos[1]
))
return runTime
@@ -0,0 +1,77 @@
# coding=utf-8
import datetime
try:
from mod.client.ui.controls.baseUIControl import *
from typing import *
except ImportError:
BaseUIControl = None
ImageUIControl = None
LabelUIControl = None
class ControlState(object):
Finish = 0
Run = 1
Suspend = 2
class Base(object):
state = ControlState.Finish
suspendTimePeriod = 0.0
runTime = datetime.datetime.now()
suspendTimePoint = runTime
def __init__(self):
self.runTime = datetime.datetime.now()
self.suspendTimePoint = self.runTime
self.suspendTimePeriod = 0.0
def Refresh(self):
"""
刷新,开始执行移动任务时需要刷新
:return:已执行任务的时间
"""
if not self.state == ControlState.Run:
return
now = (datetime.datetime.now() - self.runTime).total_seconds() - self.suspendTimePeriod
return now
def GetWorkTime(self):
"""
获取执行了多少时间
"""
return (datetime.datetime.now() - self.runTime).total_seconds() - self.suspendTimePeriod
def Suspend(self):
"""暂停任务.与完成不同的是,下一次开始时,继续执行暂停时的状态"""
self.state = ControlState.Suspend
self.suspendTimePoint = datetime.datetime.now()
def Reset(self):
"""重置已运作时间"""
self.runTime = datetime.datetime.now()
def GetState(self):
# type:()->str
"""
获取状态
"""
return self.state
def Start(self):
"""开始任务"""
if self.state == ControlState.Finish:
self.runTime = datetime.datetime.now()
self.state = ControlState.Run
elif self.state == ControlState.Suspend:
self.suspendTimePeriod += (datetime.datetime.now() - self.suspendTimePoint).total_seconds()
self.state = ControlState.Run
def Finish(self):
"""完成任务"""
self.runTime = datetime.datetime.now()
self.suspendTimePoint = self.runTime
self.suspendTimePeriod = 0.0
self.state = ControlState.Finish
# 初始化
@@ -0,0 +1,82 @@
# coding=utf-8
from BlueFoxMath import cos, sin, floor, pi, ceil, MatProduct
from ..ToolsConfig import *
def FixPosition(pos):
"""
修正坐标, 通常用于方块坐标
"""
pos = list(pos)
for index in range(pos.__len__()):
pos[index] = int(floor(pos[index]) if ceil(pos[index]) - pos[index] > 0.5 else ceil(pos[index]))
return tuple(pos)
class OriginCoord(object):
"""
对三维坐标进行原点变换
"""
def __init__(self, originPos):
self.originPos = originPos
def CalculatePos(self, pos):
"""
计算变换
"""
x, y, z = pos
return x - self.originPos[0], y - self.originPos[1], z - self.originPos[2]
def AbsPos(self, pos):
"""
逆计算变换
"""
x, y, z = pos
return x + self.originPos[0], y + self.originPos[1], z + self.originPos[2]
class RotAxes(object):
X = 0
Y = 1
Z = 2
BlockFaceRot = [90, 0, 270, 180] # 四面向方块对于不同aux对应不同旋转角度
class RotCoord(object):
"""
对三维坐标进行旋转变换
"""
def __init__(self, rot, axes):
"""
:param rot:旋转角度
:param axes:旋转轴
"""
self.rotation = rot / 180.0 * pi
if axes == RotAxes.X:
self.rotMat = [
[1, 0, 0],
[0, cos(self.rotation), -sin(self.rotation)],
[0, sin(self.rotation), cos(self.rotation)]
]
elif axes == RotAxes.Y:
self.rotMat = [
[cos(self.rotation), 0, sin(self.rotation)],
[0, 1, 0],
[-sin(self.rotation), 0, cos(self.rotation)]
]
else:
self.rotMat = [
[0, cos(self.rotation), -sin(self.rotation)],
[0, sin(self.rotation), cos(self.rotation)],
[0, 0, 1],
]
def CalculatePos(self, pos):
"""
计算变换
"""
return MatProduct(pos, self.rotMat)
@@ -0,0 +1,172 @@
# coding=utf-8
import copy
from ..ToolsConfig import *
class MountDataKeyName(object):
Animation = 'animation'
AnimationController = 'animation_controller'
ScriptAnimation = 'script_animation'
Geometry = 'geometry'
Texture = 'texture'
RenderController = 'render_controller'
Material = 'material'
class BasicMountData(dict):
"""
外挂模型动画基本数据字典
"""
def __init__(self, _data=None):
# type:(dict) -> None
if _data is None:
_data = {}
data = {
MountDataKeyName.Animation: {},
MountDataKeyName.AnimationController: {},
MountDataKeyName.ScriptAnimation: {},
MountDataKeyName.Geometry: {},
MountDataKeyName.Texture: {},
MountDataKeyName.RenderController: [],
MountDataKeyName.Material: {}
}
for kv in _data.items():
data[kv[0]] = kv[1]
super(BasicMountData, self).__init__(data)
def Copy(self):
"""
继承本字典的所有信息
"""
return copy.deepcopy(self)
def SetAnimationData(self, value):
self[MountDataKeyName.Animation] = value
return self
def AddAnimationData(self, key, value):
self[MountDataKeyName.Animation][key] = value
return self
def SetAnimationControllerData(self, value):
self[MountDataKeyName.AnimationController] = value
return self
def AddAnimationControllerData(self, key, value):
self[MountDataKeyName.AnimationController][key] = value
return self
def SetScriptAnimationData(self, value):
self[MountDataKeyName.ScriptAnimation] = value
return self
def AddScriptAnimationData(self, key, value):
self[MountDataKeyName.ScriptAnimation][key] = value
return self
def SetGeometryData(self, value):
self[MountDataKeyName.Geometry] = value
return self
def AddGeometryData(self, key, value):
self[MountDataKeyName.Geometry][key] = value
return self
def SetTextureData(self, value):
self[MountDataKeyName.Texture] = value
return self
def AddTextureData(self, key, value):
self[MountDataKeyName.Texture][key] = value
return self
def SetRenderControllerData(self, value):
self[MountDataKeyName.RenderController] = value
return self
def AddRenderControllerData(self, value):
self[MountDataKeyName.RenderController].append(value)
return self
def SetMaterialData(self, value):
self[MountDataKeyName.Material] = value
return self
def AddMaterialData(self, key, value):
self[MountDataKeyName.Material][key] = value
return self
def RemoveItem(playerId, data):
# type:(Union[str,int],BasicMountData)->None
"""
删除外挂模型
"""
playerRenderComp = clientCompFactory.CreateActorRender(playerId)
RemoveRenderController(playerRenderComp, data['render_controller'])
def LoadItem(playerId, data):
# type:(Union[str,int],BasicMountData)->None
"""
加载外挂模型贴图动画渲染器等
"""
playerRenderComp = clientCompFactory.CreateActorRender(playerId)
LoadGeometry(playerRenderComp, data['geometry'])
LoadTexture(playerRenderComp, data['texture'])
LoadRenderController(playerRenderComp, data['render_controller'])
LoadAnimation(playerRenderComp, data['animation'])
LoadAnimationController(playerRenderComp, data['animation_controller'])
LoadScriptAnimation(playerRenderComp, data['script_animation'])
LoadMaterial(playerRenderComp, data['material'])
def RemoveGeometry(playerRenderComp, data):
for _ in data.items():
playerRenderComp.RemovePlayerGeometry(_[0])
def RemoveRenderController(playerRenderComp, data):
for _ in data:
playerRenderComp.RemovePlayerRenderController(_)
def RemoveAnimationController(playerRenderComp, data):
for _ in data.items():
playerRenderComp.RemovePlayerAnimationController(_[0])
def LoadGeometry(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerGeometry(*_)
def LoadTexture(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerTexture(*_)
def LoadRenderController(playerRenderComp, data):
for _ in data:
playerRenderComp.AddPlayerRenderController(_)
def LoadAnimation(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerAnimation(*_)
def LoadAnimationController(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerAnimationController(*_)
def LoadScriptAnimation(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerScriptAnimate(*_)
def LoadMaterial(playerRenderComp, data):
for _ in data.items():
playerRenderComp.AddPlayerRenderMaterial(*_)
@@ -0,0 +1,79 @@
# coding=utf-8
from ..ToolsConfig import *
import random
def Probability(odds):
"""
根据输入概率判断一次结果
:param odds:概率float
:return:bool
"""
t = odds / 2.0
d = random.uniform(t, 1.0 - t)
return d - t < random.random() < d + t
class AddProbability(object):
"""
累加概率算法,当进行到times次时,期望行动概率为100%,但并不代表本次判定为100%
"""
def __init__(self, times):
self.runTime = 0
self.times = times
self.k = 2.0 / times ** 2.0
def ChanceOnce(self):
"""
判断一次结果
:return:bool
"""
self.runTime += 1
return Probability(self.runTime * self.k)
class AddProbabilityForMcServerTick(AddProbability):
"""
针对MC系统的特殊算法
status:R运行,S中止,F完成
"""
def __init__(self, maxTime, system, func, *args):
self.system = system
super(AddProbabilityForMcServerTick, self).__init__(int(maxTime * 30.0))
self.system.ListenForEvent(MC_SYSTEM_NAMESPACE, MC_SYSTEM_NAME, 'OnScriptTickServer', self, self.McTick)
self.func = func
self.funcParm = args
self.status = 'R'
def Suspend(self):
"""中止"""
print "递增概率函数中止:{}".format(self.func.__name__)
self.status = 'S'
self.system.UnListenForEvent(MC_SYSTEM_NAMESPACE, MC_SYSTEM_NAME, 'OnScriptTickServer', self, self.McTick)
def Start(self):
"""开始"""
print "递增概率函数中止后再次开始:{}".format(self.func.__name__)
self.status = 'R'
self.system.ListenForEvent(MC_SYSTEM_NAMESPACE, MC_SYSTEM_NAME, 'OnScriptTickServer', self, self.McTick)
def McTick(self):
self.ChanceOnce()
def ChanceOnce(self):
"""
判断一次结果
:return:bool
"""
self.runTime += 1
if Probability(self.runTime * self.k) or self.runTime >= self.times:
self.system.UnListenForEvent(MC_SYSTEM_NAMESPACE, MC_SYSTEM_NAME, 'OnScriptTickServer', self, self.McTick)
self.func(*self.funcParm)
self.status = 'F'
return True
return False
def Destroy(self):
self.system.UnListenForEvent(MC_SYSTEM_NAMESPACE, MC_SYSTEM_NAME, 'OnScriptTickServer', self, self.McTick)
@@ -0,0 +1,326 @@
# coding=utf-8
import copy
from ..OtherTools import AddTupleContext, GetDistance
from ..ToolsConfig import *
routeControls = {} # 所有路径节点
class ConnectChangedState(object):
"""
当发生连接变化时的状态
"""
DisConnect = 0b00
Connect = 0b01
ConvertConnect = 0b00
ReallyConnect = 0b10
def GetRouteByPosDim(posDim):
# type:(Union[List,Tuple]) -> RouteControl
"""
通过维度坐标获取加载的路径节点
"""
return routeControls.get(posDim)
class RouteControl(object):
"""
路径节点
"""
def __init__(self, posDim):
# type:(Tuple[int,int,int,int]) -> None
self.posDim = posDim
self.connections = [] # 真连接
self.convertConnections = [] # 隐性连接,用于记录哪个路径节点连接与此
routeControls[posDim] = self
def HasConnection(self):
"""
判断是否有方向:自身 -> routeControl的连接
"""
return self.connections.__len__() > 0
def ConnectionChanged(self, routeControl, state):
"""
当连接情况发生改变完成后触发,包括真连接和隐性连接
:param routeControl: 将要变化的路径节点
:param state: ConnectChangedState不同位的不同state
"""
pass
def Destroy(self):
"""
销毁本路径节点
:return:
"""
for routeControl in copy.copy(self.convertConnections):
# 销毁, 不可触发自身连接改变
routeControl.DisConnectBefore(self)
routeControl.connections.remove(self)
self.convertConnections.remove(routeControl)
routeControl.ConnectionChanged(self, ConnectChangedState.ReallyConnect | ConnectChangedState.DisConnect)
for routeControl in copy.copy(self.connections):
# 销毁, 不可触发自身连接改变
self.DisConnectBefore(routeControl)
self.connections.remove(routeControl)
routeControl.convertConnections.remove(self)
routeControl.ConnectionChanged(self, ConnectChangedState.ConvertConnect | ConnectChangedState.DisConnect)
del routeControls[self.posDim]
def DisconnectTo(self, routeControl):
"""
单向取消与路径节点的单向连接, 方向:自身 -> routeControl
:param routeControl:将要取消的路径节点
:return:
"""
if self.IsConnection(routeControl):
self.DisConnectBefore(routeControl)
self.connections.remove(routeControl)
routeControl.convertConnections.remove(self)
self.ConnectionChanged(routeControl, ConnectChangedState.ReallyConnect | ConnectChangedState.DisConnect)
routeControl.ConnectionChanged(self, ConnectChangedState.ConvertConnect | ConnectChangedState.DisConnect)
@staticmethod
def DisconnectBy(routeControl):
"""
单向取消与路径节点的单向连接, 方向: routeControl -> 自身
:param routeControl:将要取消的路径节点
:return:
"""
routeControl.DisconnectTo(routeControl)
def InterDisconnect(self, routeControl):
"""
双向取消与路径节点的连接
:param routeControl:将要取消的路径节点
:return:
"""
self.DisconnectTo(routeControl)
self.DisconnectBy(routeControl)
def IsConnection(self, routeControl):
"""
判断路径节点是否与本路径节点连接
:param routeControl:路径节点
:return:
"""
return routeControl in self.connections
def Connectable(self, routeControl):
"""
在尝试与对方进行连接时触发,方向:自身 -> routeControl
:param routeControl:将要连接的路径节点
:return:是否与对方连接
"""
if abs(GetDistance(self.posDim[:3], routeControl.posDim[:3]) - 1) < 10e-6:
return True
def DisConnectBefore(self, routeControl):
"""
当断开方向为:routeControl -> 自身 连接之前触发
"""
pass
def ConnectBefore(self, routeControl):
"""
当发生方向为:routeControl -> 自身 连接之前触发
"""
pass
def ConnectTo(self, routeControl):
"""
与另外一个路径节点进行单向连接,方向:自身 -> routeControl
如果routeControl不可连接与此, 将无法建立连接
"""
routeControl.ConnectBefore(self)
if self.Connectable(routeControl) and routeControl not in self.connections:
self.connections.append(routeControl)
routeControl.convertConnections.append(self)
self.ConnectionChanged(routeControl, ConnectChangedState.ReallyConnect | ConnectChangedState.Connect)
routeControl.ConnectionChanged(self, ConnectChangedState.ConvertConnect | ConnectChangedState.Connect)
def ConnectBy(self, routeControl):
"""
与另外一个路径节点进行单向连接,方向:routeControl -> 自身
"""
routeControl.ConnectTo(self)
def InterConnect(self, routeControl):
"""
与另外一个路径节点进行双向连接
"""
self.ConnectTo(routeControl)
self.ConnectBy(routeControl)
def ConnectBeside(self):
"""
与周围的可连接的路径节点进行连接
"""
myPosDim = list(self.posDim)
dr = [-1, 1]
posDimBeside = [(myPosDim[0] + x, myPosDim[1], myPosDim[2], myPosDim[3]) for x in dr] + \
[(myPosDim[0], myPosDim[1] + y, myPosDim[2], myPosDim[3]) for y in dr] + \
[(myPosDim[0], myPosDim[1], myPosDim[2] + z, myPosDim[3]) for z in dr]
for posDim in posDimBeside:
routeControl = routeControls.get(posDim)
self.InterConnect(routeControl) if routeControl else 0
def IsFinal(self, *args, **kwargs):
"""
是否为终点
:param args:判断时可能传入的参数
:param kwargs:判断时可能传入的参数
:return:
"""
return self.connections.__len__() == 1
def Finder(routeControl, maxRuntime=10e5):
# type:(RouteControl,int)->List
"""
寻找器,不可跨维度寻找
:param routeControl:起点路径节点
:param maxRuntime:最大运行次数
:return:终点集
"""
destination = [] # 目标集
x = {} # 储存已经寻找过的节点
runTime = 0 # 寻找次数
hand = routeControl.posDim # 运行指针
subRoute = [] # 下一节点
while runTime < maxRuntime:
routeControl = routeControls.get(hand) # type:RouteControl
y = x.get(hand[0], {})
z = y.get(hand[1], {})
# 如果获取错误,或者此节点已被寻找,则尝试下一个分流节点
if routeControl is None or z.get(hand[2], False):
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
continue
# 储存节点
z[hand[2]] = routeControl
y[hand[1]] = z
x[hand[0]] = y
if routeControl.IsFinal():
destination.append(routeControl)
for connection in routeControl.connections:
posDim = connection.posDim
# 判断下一节点是否被储存过
if not x.get(posDim[0], {}).get(posDim[1], {}).get(posDim[2], False):
subRoute.append(connection.posDim)
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
runTime += 1
return destination
def FinderStopWhenFindDestination(routeControl, maxRuntime=10e5):
# type:(RouteControl,int)->List
"""
寻找器,不可跨维度寻找,每个分路遇到终点会停止寻路
:param routeControl:起点路径节点
:param maxRuntime:最大运行次数
:return:终点集
"""
destination = []
x = {}
runTime = 0
hand = routeControl.posDim
subRoute = []
while runTime < maxRuntime:
routeControl = routeControls.get(hand) # type:RouteControl
y = x.get(hand[0], {})
z = y.get(hand[1], {})
if routeControl is None or z.get(hand[2], False):
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
continue
z[hand[2]] = True
y[hand[1]] = z
x[hand[0]] = y
if routeControl.IsFinal():
destination.append(routeControl)
else:
for connection in routeControl.connections:
posDim = connection.posDim
if not x.get(posDim[0], {}).get(posDim[1], {}).get(posDim[2], False):
subRoute.append(connection.posDim)
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
runTime += 1
return destination
def FinderStopSampleNum(routeControl, sampleNum, maxRuntime=10e5):
# type:(RouteControl,int,int)->Dict
"""
寻找器,不可跨维度寻找,每个分路到达sampleNumber时停止寻路
:param routeControl:起点路径节点
:param maxRuntime:最大运行次数
:param sampleNum:距离起点相隔的节点个数
:return:结果集无论是否为终点
"""
destination = {} # 目标集
x = {} # 储存已经寻找过的节点
runTime = 0 # 寻找次数
hand = routeControl.posDim # 运行指针
subRoute = [] # 下一节点
subRouteNum = {} # 下一节点计数
presentNum = 0 # 当前计数
while runTime < maxRuntime:
routeControl = routeControls.get(hand) # type:RouteControl
y = x.get(hand[0], {})
z = y.get(hand[1], {})
# 如果获取错误,或者此节点已被寻找,再或者此时计数大于sampleNum则尝试下一个分流节点
if presentNum >= sampleNum or presentNum == -1 or z.get(hand[2], False) or routeControl is None:
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
continue
# 储存节点
z[hand[2]] = routeControl
y[hand[1]] = z
x[hand[0]] = y
if presentNum > 0:
destination[presentNum] = destination.get(presentNum, [])
destination[presentNum].append(routeControl)
presentNum += 1
for connection in routeControl.connections:
posDim = connection.posDim
# 判断下一节点是否被储存过
if not x.get(posDim[0], {}).get(posDim[1], {}).get(posDim[2], False):
subRoute.append(connection.posDim)
a = subRouteNum.get(posDim[0], {})
subRouteNum[posDim[0]] = a
b = a.get(posDim[1], {})
a[posDim[1]] = b
b[posDim[2]] = presentNum
if subRoute.__len__() == 0:
break
hand = subRoute.pop()
presentNum = subRouteNum.get(hand[0], {}).get(hand[1], {}).get(hand[2], False)
runTime += 1
return destination
@@ -0,0 +1,666 @@
# -*- coding: utf-8 -*-
import math
import random
import re
import time
import mod.client.extraClientApi as clientApi
from mod.common.utils.mcmath import Matrix
from config.PlayConfig import PlayConfig
from utils.SafeEval import SafeEval
from .utils.CatmullRom import getCatmullromLerpFromKeyframe
from exception.NotPlayConfigException import NotPlayConfigException
from exception.NotClientSystemException import NotClientSystemException
compFactory = clientApi.GetEngineCompFactory()
levelId = clientApi.GetLevelId()
class Camera(object):
# 全局摄像机对象(用来判断当前客户端是否有摄像机正在播放)
globalCameraObj = None
def __init__(self, client, animationName, path='modconfigs/spring_camera_animations.json', cameraName='camera',
animationDict=None, speed=1.0):
"""
构造方法
Args:
client (ClientSystem): 客户端系统对象
animationName (str): 动画在文件中的名称
path (str, optional): 动画文件路径,需要包含 ".json"。默认值 'modconfigs/spring_camera_animations.json'
cameraName (str, optional): 动画 JSON 中的摄像机名称。默认值 'camera'
animationDict (dict, optional): 动画关键帧字典。默认值 None
speed (float, optional): 动画播放速度倍数。默认值 1.0
"""
if not isinstance(client, clientApi.GetClientSystemCls()):
raise NotClientSystemException("传入 Camera 内的 client 不是一个 ClientSystem")
# region 初始化变量
self.client = client
self.eval = SafeEval()
# 动画总时长
self.animationLength = None
# 动画旋转关键帧数组
self.animationRotationArray = None
# 动画位置关键帧数组
self.animationPositionArray = None
# 动画旋转关键帧数组的索引
self.rotationIndex = None
# 动画位置关键帧数组的索引
self.positionIndex = None
# 动画开始时间(单位:秒)
self.startAnimationTime = None
# 上一个帧的时间(单位:秒)
self.preAnimationTime = None
# 上一帧的旋转关键帧参数,形如:(0.0, [0, 180, 0], 'catmullrom')
self.preAnimationRotArgs = None
# 上一帧的位置关键帧参数,形如:(0.0, [0, 180, 0], 'catmullrom')
self.preAnimationPosArgs = None
# 动画开始前的摄像机旋转参数
self.rotationOrigin = None
# 动画开始前的摄像机锚点参数
self.positionOrigin = None
# play 配置对象
self.playConfig = PlayConfig()
# 是否正在播放摄像机动画
self.isPlaying = False
# 摄像机接口对象
self.cameraCompFactory = compFactory.CreateCamera(levelId)
# 绕 y 轴旋转的旋转矩阵
self.leftRotYMat = None
# 是否覆盖当前的位置和旋转(json 配置)
self.overridePreviousAnimation = False
# 动画循环状态:播放一次、停止于最后一帧、循环播放
self.loop = None
# 防止停不住
self.playTimer = None
# 前一帧设置的摄像机旋转角度(目前仅用于将当前帧设置的旋转角度传递给后续的 GameRenderTickEvent 事件)
self.beforeRot = None
# 是否正在暂停
self.isPausing = False
# 动画暂停开始时间
self.pauseStartTime = 0.0
# endregion
# 动画关键帧字典
if not animationDict:
animationDict = clientApi.GetModConfigJson(path)['animations'][animationName]
self.animationLength = animationDict.get('animation_length')
if not self.animationLength:
# 无时间的动画,默认动画时长 24 小时
self.animationLength = 86400.0
# 应用速度倍数
speed = 1.0 / speed
self.animationLength *= speed
self.overridePreviousAnimation = animationDict['override_previous_animation'] if animationDict.__contains__(
'override_previous_animation') else False
self.loop = animationDict['loop'] if animationDict.__contains__('loop') else False
if animationDict['bones'][cameraName].__contains__('rotation'):
animationRotationDict = animationDict['bones'][cameraName]['rotation']
# 解析只有单个关键帧的动画
if isinstance(animationRotationDict, list):
animationRotationDict = {'0.0': animationRotationDict}
# 动画旋转关键帧数组
self.animationRotationArray = sorted(animationRotationDict.items(), key=lambda item: float(item[0]))
self.animationRotationArray = [
(float(item[0]) * speed,
item[1]['post'] if isinstance(item[1], dict) else item[1],
item[1].get('lerp_mode') if isinstance(item[1], dict) else None)
for item in self.animationRotationArray
]
self._parseRotKeyframe(self.animationRotationArray)
if animationDict['bones'][cameraName].__contains__('position'):
animationPositionDict = animationDict['bones'][cameraName]['position']
# 解析只有单个关键帧的动画
if isinstance(animationPositionDict, list):
animationPositionDict = {'0.0': animationPositionDict}
# 动画位置关键帧数组
self.animationPositionArray = sorted(animationPositionDict.items(), key=lambda item: float(item[0]))
self.animationPositionArray = [
(float(item[0]) * speed,
item[1]['post'] if isinstance(item[1], dict) else item[1],
item[1].get('lerp_mode') if isinstance(item[1], dict) else None)
for item in self.animationPositionArray
]
self._parsePosKeyframe(self.animationPositionArray)
def _parseRotKeyframe(self, animationArray):
"""
转换旋转关键帧中的字符串
Args:
animationArray (list): 旋转关键帧数组
"""
rot = compFactory.CreateCamera(levelId).GetCameraRotation()
self._parseKeyframe(animationArray, rot)
def _parsePosKeyframe(self, animationArray):
"""
转换位置关键帧中的字符串
Args:
animationArray (list): 位置关键帧数组
"""
pos = compFactory.CreatePos(clientApi.GetLocalPlayerId()).GetFootPos()
self._parseKeyframe(animationArray, (pos[0] * 16.0, pos[1] * 16.0, pos[2] * 16.0))
def _parseKeyframe(self, animationArray, replaceThis):
"""
转换关键帧中的字符串
Args:
animationArray (list): 关键帧数组
replaceThis (tuple): 替换 this 的元组
"""
for i in xrange(0, len(animationArray)):
for j in xrange(0, len(animationArray[i][1])):
if not isinstance(animationArray[i][1][j], str):
continue
if 'this' in animationArray[i][1][j]:
animationArray[i][1][j] = animationArray[i][1][j].replace('this', format(replaceThis[j], '.16f'))
animationArray[i][1][j] = self.eval.evaluate(animationArray[i][1][j])
elif 'math.random' in animationArray[i][1][j]:
match = re.search(r'(-?\d+(\.\d+)?),\s*(-?\d+(\.\d+)?)', animationArray[i][1][j])
if match:
animationArray[i][1][j] = random.uniform(float(match.group(1)), float(match.group(3)))
def _indexOutEnd(self):
"""
防止下标越界
"""
# 如果数组下标越界,设置下标为最后一个元素
if self.animationRotationArray and self.rotationIndex >= len(self.animationRotationArray):
self.rotationIndex = len(self.animationRotationArray) - 1
if self.animationPositionArray and self.positionIndex >= len(self.animationPositionArray):
self.positionIndex = len(self.animationPositionArray) - 1
def _leftRotationMatrixY(self, angleDegrees):
"""
生成绕 Y 轴旋转指定角度的 3x3 旋转矩阵(左手坐标系)
Args:
angleDegrees (float): 旋转角度
Returns:
Matrix: 3x3 旋转矩阵
"""
# 将角度转换为弧度
angleRad = math.radians(angleDegrees)
# 计算余弦和正弦值
cosAngle = math.cos(angleRad)
sinAngle = math.sin(angleRad)
# 构建绕 Y 轴旋转矩阵(左手坐标系)
rotationMatrix = Matrix.Create([
[cosAngle, 0, -sinAngle],
[0, 1, 0, ],
[sinAngle, 0, cosAngle]
])
return rotationMatrix
def _leftRotationMatrixX(self, angleDegrees):
"""
生成绕 X 轴旋转指定角度的 3x3 旋转矩阵(左手坐标系)
Args:
angleDegrees (float): 旋转角度
Returns:
Matrix: 3x3 旋转矩阵
"""
# 将角度转换为弧度
angleRad = math.radians(angleDegrees)
# 计算余弦和正弦值
cosAngle = math.cos(angleRad)
sinAngle = math.sin(angleRad)
# 构建绕 X 轴旋转矩阵(左手坐标系)
rotationMatrix = Matrix.Create([
[1, 0, 0],
[0, cosAngle, sinAngle],
[0, -sinAngle, cosAngle]
])
return rotationMatrix
def _getNowTime(self):
"""
获取当前时间
"""
if self.isPausing and self.preAnimationTime is not None:
return self.preAnimationTime
return time.time()
def playFirstPerson(self, playAfterFunc=None):
"""
播放第一人称动画(允许拖拽,允许移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(True, True, True, False, True, playAfterFunc=playAfterFunc)
def playFirstPersonDisableMove(self, playAfterFunc=None):
"""
播放第一人称动画(允许拖拽,禁止移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(True, True, False, False, True, playAfterFunc=playAfterFunc)
def playFirstPersonDisableDrag(self, playAfterFunc=None):
"""
播放第一人称动画(禁止拖拽,允许移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(True, False, True, False, True, playAfterFunc=playAfterFunc)
def playFirstPersonDisableDragWithMove(self, playAfterFunc=None):
"""
播放第一人称动画(禁止拖拽,禁止移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(True, False, False, False, True, playAfterFunc=playAfterFunc)
def playThirdPerson(self, playAfterFunc=None):
"""
播放第三人称动画(允许拖拽,允许移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(False, True, True, playAfterFunc=playAfterFunc)
def playThirdPersonDisableMove(self, playAfterFunc=None):
"""
播放第三人称动画(允许拖拽,禁止移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(False, True, False, playAfterFunc=playAfterFunc)
def playThirdPersonDisableDrag(self, playAfterFunc=None):
"""
播放第三人称动画(禁止拖拽,允许移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(False, False, True, playAfterFunc=playAfterFunc)
def playThirdPersonDisableDragWithMove(self, playAfterFunc=None):
"""
播放第三人称动画(禁止拖拽,禁止移动)
Args:
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.play(False, False, False, playAfterFunc=playAfterFunc)
def play(self, isFirstPerson=True, isAllowDrag=True, isAllowMove=True, isPosFollowXRot=True, isPosFollowYRot=True,
isZRotForceReturn=True, isZRotStartOrigin=False,
isDelayOneFrame=False, isLockPerspective=False, tickPosFunc=None, tickRotFunc=None, playAfterFunc=None):
"""
播放摄像机动画
Args:
isFirstPerson (bool, optional): 是否是第一人称动画。如果是,那么 Y 轴旋转会减 180 度。默认值 False
isAllowDrag (bool, optional): 是否允许玩家拖拽视角。默认值 False
isAllowMove (bool, optional): 是否允许玩家移动。默认值 False
isPosFollowXRot (bool, optional): 摄像机空间坐标是否受 X 轴旋转影响(仅在第一人称下生效)。默认值 True
isPosFollowYRot (bool, optional): 摄像机空间坐标是否受 Y 轴旋转影响(仅在第一人称下生效)。默认值 True
isZRotForceReturn (bool, optional): 是否在动画播放完成后强制归零 Z 轴的旋转值。默认值 True
isZRotStartOrigin (bool, optional): 是否将动画播放前的 Z 轴旋转值作为初始的 Z 轴旋转值。置为 False 可防止玩家在受伤瞬间播放摄像机动画造成的 Z 轴旋转异常(json 配置 override_previous_animation 值为 True 时无效)。默认值 False
isDelayOneFrame (bool, optional): 是否延迟一帧再播放摄像机动画。默认值 True
isLockPerspective (bool, optional): 是否锁定并强制切换玩家视角。默认值 True
tickPosFunc (function, optional): 摄像机动画播放时,在每次设置摄像机位置前执行的函数。默认值 None
tickRotFunc (function, optional): 摄像机动画播放时,在每次设置摄像机旋转前执行的函数。默认值 None
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.playWithConfig(
PlayConfig(isFirstPerson, isAllowDrag, isAllowMove, isPosFollowXRot, isPosFollowYRot, isZRotForceReturn,
isZRotStartOrigin, isDelayOneFrame, isLockPerspective,
tickPosFunc, tickRotFunc, playAfterFunc))
def playWithConfig(self, config):
"""
使用播放配置对象播放摄像机动画
Args:
config (PlayConfig): 播放配置对象
"""
if not isinstance(config, PlayConfig):
raise NotPlayConfigException("传入 playWithConfig 内的 config 不是一个 PlayConfig")
if Camera.globalCameraObj:
# 强制停止正在播放的摄像机动画(全局)
Camera.globalCameraObj.stop(True)
# 开始监听
from .CameraListen import _enable
if not _enable:
self.client.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"GameRenderTickEvent", self, self.GameRenderTickEvent, 10)
self.client.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"HealthChangeClientEvent", self, self.HealthChangeClientEvent)
def realPlay():
# 初始化相关变量
self.playConfig = config
self.rotationIndex = 0
self.positionIndex = 0
operationComp = compFactory.CreateOperation(levelId)
# 不允许玩家移动
if not self.playConfig.isAllowMove:
operationComp.SetCanMove(False)
operationComp.SetCanJump(False)
operationComp.SetCanInair(False)
# 不允许玩家拖拽
if not self.playConfig.isAllowDrag:
operationComp.SetCanDrag(False)
# 不允许玩家切换视角
if self.playConfig.isLockPerspective:
operationComp.SetCanPerspective(False)
# 第三人称
if not self.playConfig.isFirstPerson:
if self.playConfig.isLockPerspective:
compFactory.CreatePlayerView(clientApi.GetLocalPlayerId()).SetPerspective(1)
self.cameraCompFactory.SetCameraDistanceFixed(True)
self.cameraCompFactory.DepartCamera()
self.cameraCompFactory.SetCameraOffset((0, 0.01, -4))
else:
if self.playConfig.isLockPerspective:
compFactory.CreatePlayerView(clientApi.GetLocalPlayerId()).SetPerspective(0)
if self.animationRotationArray:
self.preAnimationRotArgs = (float(self.animationRotationArray[self.rotationIndex][0]),
self.animationRotationArray[self.rotationIndex][1])
if self.animationPositionArray:
self.preAnimationPosArgs = (float(self.animationPositionArray[self.positionIndex][0]),
self.animationPositionArray[self.positionIndex][1])
self.preAnimationTime = self.startAnimationTime = time.time()
# 是否覆盖
if self.overridePreviousAnimation:
self.rotationOrigin = (0, 0, 0)
self.positionOrigin = (0, 0, 0)
else:
self.rotationOrigin = self.cameraCompFactory.GetCameraRotation()
self.positionOrigin = self.cameraCompFactory.GetCameraAnchor()
# 可防止玩家在受伤瞬间播放摄像机动画造成的 Z 轴旋转异常
if not self.playConfig.isZRotStartOrigin:
self.rotationOrigin = (self.rotationOrigin[0], self.rotationOrigin[1], 0.0)
# 绕 y 轴的旋转矩阵
self.leftRotYMat = self._leftRotationMatrixY(self.rotationOrigin[1])
# 一定要在最后给 isPlaying 赋值
Camera.globalCameraObj = self
self.isPlaying = True
if config.isDelayOneFrame:
# 延时一帧再播放(为了确保上一个动画完全停止,使得下一次获取初始数据时不受上次动画影响)
compGame = compFactory.CreateGame(levelId)
self.playTimer = compGame.AddTimer(0.0, realPlay)
else:
realPlay()
def destroyListen(self):
"""
销毁内置监听
"""
self.client.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"GameRenderTickEvent", self, self.GameRenderTickEvent)
self.client.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(),
"HealthChangeClientEvent", self, self.HealthChangeClientEvent)
def stop(self, isStopLoop=False):
"""
停止摄像机动画
Args:
isStopLoop (bool, optional): 是否停止循环动画(仅对 json 中 loop 配置为 True 的动画有效)。默认值 False
"""
if self.playTimer: compFactory.CreateGame(levelId).CancelTimer(self.playTimer)
# 取消监听
from .CameraListen import _enable
if not _enable:
self.destroyListen()
self.isPlaying = False
self.isPausing = False
Camera.globalCameraObj = None
# region 摄像机还原
# 第三人称
if not self.playConfig.isFirstPerson:
if self.playConfig.isLockPerspective:
compFactory.CreatePlayerView(clientApi.GetLocalPlayerId()).SetPerspective(0)
self.cameraCompFactory.SetCameraDistanceFixed(False)
self.cameraCompFactory.UnDepartCamera()
self.cameraCompFactory.SetCameraOffset((0, 0, 0))
# 根据 loop 的值进行设置
nowCameraRotation = self.rotationOrigin
nowCameraPosition = self.positionOrigin
if self.loop == 'hold_on_last_frame' or self.loop == True or self.playConfig.isAllowDrag:
nowCameraRotation = self.cameraCompFactory.GetCameraRotation()
if not self.playConfig.isAllowDrag:nowCameraPosition = self.cameraCompFactory.GetCameraAnchor()
# 是否强制回正 Z 轴
if self.playConfig.isZRotForceReturn:
self.cameraCompFactory.SetCameraRotation((nowCameraRotation[0], nowCameraRotation[1], 0))
else:
self.cameraCompFactory.SetCameraRotation(nowCameraRotation)
self.cameraCompFactory.SetCameraAnchor(nowCameraPosition)
# endregion
# 判断是否循环播放
if self.loop == True and not isStopLoop:
self.playWithConfig(self.playConfig)
return
operationComp = compFactory.CreateOperation(levelId)
# 恢复玩家移动
operationComp.SetCanMove(True)
operationComp.SetCanJump(True)
operationComp.SetCanInair(True)
operationComp.SetCanDrag(True)
# 允许玩家切换视角
operationComp.SetCanPerspective(True)
# 动画停止播放后执行传入的回调函数
if (self.loop == True and isStopLoop) or (self.loop != True):
if callable(self.playConfig.playAfterFunc):
self.playConfig.playAfterFunc()
def pause(self, resumeTime=0):
# type: (float) -> None
"""
暂停摄像机动画
"""
if self.isPausing:
return
self.isPausing = True
self.pauseStartTime = time.time()
# 多少秒后恢复动画播放
if resumeTime > 0:
compFactory.CreateGame(levelId).AddTimer(resumeTime, self.resume)
def resume(self):
"""
继续播放摄像机动画
"""
if not self.isPausing:
return
self.isPausing = False
pauseTime = time.time() - self.pauseStartTime
self.startAnimationTime += pauseTime
self.preAnimationTime += pauseTime
def GameRenderTickEvent(self, event):
"""
每 Tick 触发
"""
if not self.isPlaying:
return
nowAnimationTime = self._getNowTime()
startToNowTime = nowAnimationTime - self.startAnimationTime # 当前时间距离动画开始的时间
preToNowTime = nowAnimationTime - self.preAnimationTime # 当前时间距离上一帧的时间
self.preAnimationTime = nowAnimationTime # 更新上一帧的时间为当前时间,为下一次计算做准备
# 如果自动画开始以来经过的时间,大于动画总时长,则动画结束
if startToNowTime > self.animationLength:
self.stop()
return
self._indexOutEnd()
# 如果自动画开始以来经过的时间,大于当前关键帧时间,说明关键帧索引应该增加
while self.animationRotationArray and self.rotationIndex < len(
self.animationRotationArray) and startToNowTime > float(
self.animationRotationArray[self.rotationIndex][0]):
# 1.0.3 修复上个关键帧没执行到结尾就计算下个关键帧所带来的问题
self.preAnimationRotArgs = (self.preAnimationRotArgs[0], self.animationRotationArray[self.rotationIndex][1])
self.rotationIndex += 1
while self.animationPositionArray and self.positionIndex < len(
self.animationPositionArray) and startToNowTime > float(
self.animationPositionArray[self.positionIndex][0]):
# 1.0.3 修复上个关键帧没执行到结尾就计算下个关键帧所带来的问题
self.preAnimationPosArgs = (self.preAnimationPosArgs[0], self.animationPositionArray[self.positionIndex][1])
self.positionIndex += 1
self._indexOutEnd()
# 计算位置
if self.animationPositionArray:
keyframePosTime = float(self.animationPositionArray[self.positionIndex][0]) - self.preAnimationPosArgs[
0] # 位置关键帧下个关键帧与上个关键帧之间的时间
# 只有时间间隔大于 0,才计算当前这一刻的值
if keyframePosTime > 0.0:
positionX = 0
positionY = 0
positionZ = 0
if self.animationPositionArray[self.positionIndex][2] == 'catmullrom':
positionX = getCatmullromLerpFromKeyframe(self.animationPositionArray, self.positionIndex,
startToNowTime, 0)
positionY = getCatmullromLerpFromKeyframe(self.animationPositionArray, self.positionIndex,
startToNowTime, 1)
positionZ = getCatmullromLerpFromKeyframe(self.animationPositionArray, self.positionIndex,
startToNowTime, 2)
else:
positionX = ((float(self.animationPositionArray[self.positionIndex][1][0]) -
self.preAnimationPosArgs[1][0]) / keyframePosTime * preToNowTime) + \
self.preAnimationPosArgs[1][0]
positionY = ((float(self.animationPositionArray[self.positionIndex][1][1]) -
self.preAnimationPosArgs[1][1]) / keyframePosTime * preToNowTime) + \
self.preAnimationPosArgs[1][1]
positionZ = ((float(self.animationPositionArray[self.positionIndex][1][2]) -
self.preAnimationPosArgs[1][2]) / keyframePosTime * preToNowTime) + \
self.preAnimationPosArgs[1][2]
pos = (
(1 if self.playConfig.isFirstPerson else -1) * positionX / 16.0,
positionY / 16.0 - 1.62,
(-1 if self.playConfig.isFirstPerson else 1) * positionZ / 16.0
)
posMat = Matrix.Create([
[pos[0]],
[pos[1]],
[pos[2]]
])
if self.playConfig.isFirstPerson:
# 第一人称
rotMax = Matrix.CreateEye(3)
nowCameraRotation = self.cameraCompFactory.GetCameraRotation()
deltX = (nowCameraRotation[0] - self.preAnimationRotArgs[1][0])
deltY = (nowCameraRotation[1] - self.preAnimationRotArgs[1][1])
# 注意矩阵相乘顺序
if self.playConfig.isPosFollowXRot:
rotMax = self._leftRotationMatrixX(deltX)
if self.playConfig.isPosFollowYRot:
rotMax = self._leftRotationMatrixY(deltY - 180) * rotMax
posMat = rotMax * posMat
else:
# 第三人称位置只受初始绕 y 轴旋转的影响
posMat = self.leftRotYMat * posMat
pos = (
posMat[0, 0] + self.positionOrigin[0],
posMat[1, 0] + self.positionOrigin[1],
posMat[2, 0] + self.positionOrigin[2]
)
# 是否不设置
isNotSet = False
if callable(self.playConfig.tickPosFunc):
isNotSet = self.playConfig.tickPosFunc(startToNowTime, pos)
if not isNotSet:
self.cameraCompFactory.SetCameraAnchor(pos)
self.preAnimationPosArgs = (startToNowTime, [positionX, positionY, positionZ])
# 计算旋转
if self.animationRotationArray:
keyframeRotTime = float(self.animationRotationArray[self.rotationIndex][0]) - self.preAnimationRotArgs[
0] # 旋转关键帧下个关键帧与上个关键帧之间的时间
# 只有时间间隔大于 0,才计算当前这一刻的值
if keyframeRotTime > 0.0:
rotationX = 0
rotationY = 0
rotationZ = 0
if self.animationRotationArray[self.rotationIndex][2] == 'catmullrom':
rotationX = getCatmullromLerpFromKeyframe(self.animationRotationArray, self.rotationIndex,
startToNowTime, 0)
rotationY = getCatmullromLerpFromKeyframe(self.animationRotationArray, self.rotationIndex,
startToNowTime, 1)
rotationZ = getCatmullromLerpFromKeyframe(self.animationRotationArray, self.rotationIndex,
startToNowTime, 2)
else:
rotationX = ((float(self.animationRotationArray[self.rotationIndex][1][0]) -
self.preAnimationRotArgs[1][0]) / keyframeRotTime * preToNowTime) + \
self.preAnimationRotArgs[1][0]
rotationY = ((float(self.animationRotationArray[self.rotationIndex][1][1]) -
self.preAnimationRotArgs[1][1]) / keyframeRotTime * preToNowTime) + \
self.preAnimationRotArgs[1][1]
rotationZ = ((float(self.animationRotationArray[self.rotationIndex][1][2]) -
self.preAnimationRotArgs[1][2]) / keyframeRotTime * preToNowTime) + \
self.preAnimationRotArgs[1][2]
deltX = self.rotationOrigin[0] if self.playConfig.isFirstPerson else 0
deltY = self.rotationOrigin[1] - (180 if self.playConfig.isFirstPerson else 0)
if self.playConfig.isAllowDrag:
nowCameraRotation = self.cameraCompFactory.GetCameraRotation()
deltX = (nowCameraRotation[0] - self.preAnimationRotArgs[1][0])
deltY = (nowCameraRotation[1] - self.preAnimationRotArgs[1][1])
deltX += rotationX
deltY += rotationY
rot = (
deltX % (-360 if deltX < 0 else 360),
deltY % (-360 if deltY < 0 else 360),
# 军械库版本,因为摄像机动画直接使用 Java 版动画,所以 y 轴没有旋转 180 度,因此 z 轴旋转从 - 改为 +,以匹配 Blockbench 里的效果
self.rotationOrigin[2] + rotationZ
)
# 是否不设置
isNotSet = False
if callable(self.playConfig.tickRotFunc):
isNotSet = self.playConfig.tickRotFunc(startToNowTime, rot)
if not isNotSet:
self.cameraCompFactory.SetCameraRotation(rot)
self.beforeRot = rot
self.preAnimationRotArgs = (
startToNowTime, [rotationX, rotationY, rotationZ]) # 更新上一帧的参数为当前参数,为下一次计算做准备
def HealthChangeClientEvent(self, event):
"""
生物生命值发生变化时触发
"""
# 自己死亡时,停止当前摄像机动画
playerType = clientApi.GetMinecraftEnum().EntityType.Player
if event['entityId'] == clientApi.GetLocalPlayerId() and compFactory.CreateEngineType(
event['entityId']).GetEngineType() & playerType == playerType and event['to'] <= 0.0:
self.stop(True)
@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
import time
import mod.client.extraClientApi as clientApi
from .Camera import Camera, compFactory
# noinspection PyMethodMayBeStatic
class CameraListen(object):
"""
摄像机监听类,创建此类的对象,以手动控制摄像机的监听与取消监听
"""
cameraListenObj = None # type: CameraListen
def __init__(self):
pass
def GameRenderTickEvent(self, event):
if Camera.globalCameraObj:
Camera.globalCameraObj.GameRenderTickEvent(event)
def HealthChangeClientEvent(self, event):
if Camera.globalCameraObj:
Camera.globalCameraObj.HealthChangeClientEvent(event)
# 单个实例
CameraListen.cameraListenObj = CameraListen()
# 取消监听定时器
_timer = None
# 是否开启了内置监听
_enable = False
def startListen(client):
"""
开始监听
"""
global _enable
cameraListenObj = CameraListen.cameraListenObj
# 销毁自身已有监听
stopListen(client)
# 销毁摄像机对象内置监听
if Camera.globalCameraObj:
Camera.globalCameraObj.destroyListen()
_enable = True
client.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "GameRenderTickEvent", cameraListenObj, cameraListenObj.GameRenderTickEvent, 10)
client.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "HealthChangeClientEvent", cameraListenObj, cameraListenObj.HealthChangeClientEvent)
def stopListen(client, delayTime=0):
"""
取消监听
"""
global _timer
cameraListenObj = CameraListen.cameraListenObj
comp = compFactory.CreateGame(clientApi.GetLevelId())
def _scheduleCancel():
global _timer, _enable
# 如果取消监听时,发现还存在未停止的摄像机动画,不取消监听,再次创建一个定时器
if Camera.globalCameraObj and delayTime > 0:
if _timer:
_timer = comp.CancelTimer(_timer)
_timer = comp.AddTimer(delayTime, _scheduleCancel)
return
_enable = False
client.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "GameRenderTickEvent", cameraListenObj, cameraListenObj.GameRenderTickEvent)
client.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "HealthChangeClientEvent", cameraListenObj,
cameraListenObj.HealthChangeClientEvent)
if delayTime > 0:
_timer = comp.AddTimer(delayTime, _scheduleCancel)
else:
if _timer:
_timer = comp.CancelTimer(_timer)
_scheduleCancel()
def stopListenKeepLastAnim(client):
"""
取消监听,并且自动保留取消监听前最后一个摄像机动画的完整播放
"""
if Camera.globalCameraObj and Camera.globalCameraObj.animationLength is not None and Camera.globalCameraObj.startAnimationTime is not None:
t = Camera.globalCameraObj.animationLength - (time.time() - Camera.globalCameraObj.startAnimationTime)
stopListen(client, t)
else:
stopListen(client)
@@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
import math
import time
import mod.client.extraClientApi as clientApi
from .utils.CatmullRom import getCatmullromLerpFromKeyframe
from .Camera import Camera, levelId, compFactory
from .exception.NotClientSystemException import NotClientSystemException
class Interpolation(object):
# 全局对象
globalObj = None
def __init__(self, client, keyframes, isReturn=False):
if not isinstance(client, clientApi.GetClientSystemCls()):
raise NotClientSystemException("传入 Interpolation 内的 client 不是一个 ClientSystem")
self.client = client
# 开始时间
self.startTime = time.time()
# 更新时间(再次调用 setInterpolation 时的时间)
self.updateTime = self.startTime
# 关键帧数组
self.keyframes = keyframes
# 关键帧数组索引
self.index = 0
# 前一帧索引,用于关键帧循环
self.preIndex = 0
# 前一帧计算出的旋转插值
self.preRot = (0, 0)
# 用于回正插值的缓存
self.tempRot = [0, 0, 0]
# 当前是否是回正插值
self.isReturn = isReturn
# 前一帧的摄像机角度
self.beforeRot = None
# 是否取消回正插值
self.cancelReturn = False
# 摄像机接口对象
self.cameraComponentFactory = compFactory.CreateCamera(levelId)
self.client.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "GameRenderTickEvent", self, self.onGameRenderTick, 0)
def onGameRenderTick(self, event):
"""
每 Tick 触发
"""
nowTime = time.time()
# 回正超时后自动停止
if self.isReturn and nowTime - self.startTime > self.keyframes[-1][0]:
self.stop()
# 当发现关键帧循环时,清空特定变量
if self.index < self.preIndex:
self.preRot = (0, 0)
startToNowTime = min(
nowTime - self.updateTime + self.keyframes[min(max(self.index - 1, 0), len(self.keyframes) - 1)][0],
self.keyframes[self.index][0]
)
rotX = getCatmullromLerpFromKeyframe(self.keyframes, self.index, startToNowTime, 0)
rotY = getCatmullromLerpFromKeyframe(self.keyframes, self.index, startToNowTime, 1)
nowRot = self.cameraComponentFactory.GetCameraRotation()
# 比较摄像机前后帧是否有较大的偏差,如果有,认为是玩家拖动屏幕造成的,取消回正
if self.beforeRot:
tolerance = 0.3
# y 轴角度是 -180 到 180 的循环,所以在计算角度差时要特殊处理
y = abs(nowRot[1] - self.beforeRot[1])
if abs(nowRot[0] - self.beforeRot[0]) > tolerance or min(y, 360 - y) > tolerance:
self.cancelReturn = True
# 混合之前的事件中设置的摄像机旋转
beforeEventRot = (0, 0, 0)
if Camera.globalCameraObj and Camera.globalCameraObj.beforeRot:
beforeEventRot = Camera.globalCameraObj.beforeRot
tempRot = (
rotX - self.preRot[0],
rotY - self.preRot[1],
0
)
deltX = nowRot[0] + tempRot[0]
deltY = nowRot[1] + tempRot[1]
rot = (
deltX % (-360 if deltX < 0 else 360),
deltY % (-360 if deltY < 0 else 360),
tempRot[2] + beforeEventRot[2]
)
self.cameraComponentFactory.SetCameraRotation(rot)
self.beforeRot = rot
self.preRot = (rotX, rotY)
self.preIndex = self.index
self.tempRot[0] -= tempRot[0]
self.tempRot[1] -= tempRot[1]
self.tempRot[2] -= tempRot[2]
def stop(self):
self.client.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "GameRenderTickEvent", self, self.onGameRenderTick)
Interpolation.globalObj = None
if not self.isReturn and not self.cancelReturn:
# 启动回正
endTime = math.sqrt(self.tempRot[0] * self.tempRot[0] + self.tempRot[1] * self.tempRot[1] + self.tempRot[2] * self.tempRot[2])
endTime *= 0.3
keyframes = [
(0.0, [0, 0, 0]),
(endTime, self.tempRot)
]
setInterpolation(self.client, keyframes, 1, isReturn=True)
def setInterpolation(client, keyframes, index, speed=10.0, isReturn=False):
"""
设置插值
Args:
client (ClientSystem): 客户端对象
keyframes (list): 关键帧数组,格式:[(0.0, [1.0, 2.5, 3.3]),(1.0, [-3.0, 2.5, 3.3])]
index (int): 关键帧数组索引
speed (float, optional): 速度。默认值 10.0
isReturn (bool, optional): 是否是回正插值。默认值 False
"""
if not Interpolation.globalObj:
keyframes = [(t / speed, i) for t, i in keyframes]
Interpolation.globalObj = Interpolation(client, keyframes, isReturn)
elif Interpolation.globalObj.isReturn:
# 正在回正中,强制停止
Interpolation.globalObj.stop()
keyframes = [(t / speed, i) for t, i in keyframes]
Interpolation.globalObj = Interpolation(client, keyframes, isReturn)
Interpolation.globalObj.updateTime = time.time()
Interpolation.globalObj.index = index
def stopInterpolation():
"""
停止插值
"""
if Interpolation.globalObj:
Interpolation.globalObj.stop()
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
class PlayConfig(object):
def __init__(self, isFirstPerson = False, isAllowDrag = False, isAllowMove = False, isPosFollowXRot = True, isPosFollowYRot = True, isZRotForceReturn = True, isZRotStartOrigin = False, isDelayOneFrame = True, isLockPerspective = True, tickPosFunc = None, tickRotFunc = None, playAfterFunc = None):
"""
play 方法的参数对象
Args:
isFirstPerson (bool, optional): 是否是第一人称动画。如果是,那么 Y 轴旋转会减 180 度。默认值 False
isAllowDrag (bool, optional): 是否允许玩家拖拽视角。默认值 False
isAllowMove (bool, optional): 是否允许玩家移动。默认值 False
isPosFollowXRot (bool, optional): 摄像机空间位置坐标是否受 X 轴旋转影响(仅在第一人称下生效)。默认值 True
isPosFollowYRot (bool, optional): 摄像机空间位置坐标是否受 Y 轴旋转影响(仅在第一人称下生效)。默认值 True
isZRotForceReturn (bool, optional): 是否在动画播放完成后强制归零 Z 轴的旋转值。默认值 True
isZRotStartOrigin (bool, optional): 是否将动画播放前的 Z 轴旋转值作为初始的 Z 轴旋转值。置为 False 可防止玩家在受伤瞬间播放摄像机动画造成的 Z 轴旋转异常(json 配置 override_previous_animation 值为 True 时无效)。默认值 False
isDelayOneFrame (bool, optional): 是否延迟一帧再播放摄像机动画。默认值 True
isLockPerspective (bool, optional): 是否锁定并强制切换玩家视角。默认值 True
tickPosFunc (function, optional): 摄像机动画播放时,在每次设置摄像机位置前执行的函数。默认值 None
tickRotFunc (function, optional): 摄像机动画播放时,在每次设置摄像机旋转前执行的函数。默认值 None
playAfterFunc (function, optional): 摄像机动画播放结束后的回调函数。默认值 None
"""
self.isFirstPerson = isFirstPerson
self.isAllowDrag = isAllowDrag
self.isAllowMove = isAllowMove
self.isPosFollowXRot = isPosFollowXRot
self.isPosFollowYRot = isPosFollowYRot
self.isZRotForceReturn = isZRotForceReturn
self.isZRotStartOrigin = isZRotStartOrigin
self.isDelayOneFrame = isDelayOneFrame
self.isLockPerspective = isLockPerspective
self.tickPosFunc = tickPosFunc
self.tickRotFunc = tickRotFunc
self.playAfterFunc = playAfterFunc
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
class NotClientSystemException(Exception):
"""
不是客户端系统异常
"""
def __init__(self, errorMessage):
super(NotClientSystemException, self).__init__(errorMessage)
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
class NotPlayConfigException(Exception):
"""
不是播放配置对象异常
"""
def __init__(self, errorMessage):
super(NotPlayConfigException, self).__init__(errorMessage)
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
class SafeEvalException(Exception):
"""
安全计算异常
"""
def __init__(self, errorMessage):
super(SafeEvalException, self).__init__(errorMessage)
@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
class SplineCurve:
"""
Catmull-Rom 样条曲线类
"""
def __init__(self, points):
"""
初始化样条曲线
points: 值列表
"""
self.points = points
def getPoint(self, t):
"""根据参数 t 获取曲线上的点"""
if len(self.points) < 2:
return 0
# 处理边界情况
if t <= 0:
return self.points[0]
if t >= 1:
return self.points[-1]
# 找到包含 t 的线段
numSegments = len(self.points) - 1
segmentIndex = int(t * numSegments)
segmentIndex = max(0, min(segmentIndex, numSegments - 1))
# 计算线段内的局部参数
localT = t * numSegments - segmentIndex
# 获取四个控制点用于 Catmull-Rom 插值
p0Index = max(0, segmentIndex - 1)
p1Index = segmentIndex
p2Index = segmentIndex + 1
p3Index = min(len(self.points) - 1, segmentIndex + 2)
p0 = self.points[p0Index]
p1 = self.points[p1Index]
p2 = self.points[p2Index]
p3 = self.points[p3Index]
# Catmull-Rom 插值公式
t2 = localT * localT
t3 = t2 * localT
# 计算插值点的 y 坐标
y = 0.5 * (
(2 * p1) +
(-p0 + p2) * localT +
(2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
(-p0 + 3 * p1 - 3 * p2 + p3) * t3
)
return y
def getCatmullromLerp(beforePlus, before, after, afterPlus, axis, alpha):
"""
Catmull-Rom 插值函数
Args:
beforePlus: 前一个关键帧的前一个关键帧
before: 前一个关键帧
after: 后一个关键帧
afterPlus: 后一个关键帧的后一个关键帧
axis: 坐标轴 (x: 0, y: 1, z: 2)
alpha: 插值参数 (0-1)
Returns:
插值结果
"""
vectors = []
# 构建控制点向量
if beforePlus:
vectors.append(beforePlus[1][axis])
if before:
vectors.append(before[1][axis])
if after:
vectors.append(after[1][axis])
if afterPlus:
vectors.append(afterPlus[1][axis])
# 创建样条曲线
curve = SplineCurve(vectors)
# 计算时间参数
timeParam = (alpha + (1 if beforePlus else 0)) / (len(vectors) - 1) if len(vectors) > 1 else 0
# 获取插值点并返回 y 坐标
point = curve.getPoint(timeParam)
return point
def getCatmullromLerpFromKeyframe(keyframeArray, index, nowTime, axis):
"""
通过关键帧数组获取当前时间点的平滑插值
"""
if not keyframeArray:
return 0
endIndex = len(keyframeArray) - 1
kf0 = keyframeArray[max(index - 2, 0)]
kf1 = keyframeArray[max(index - 1, 0)]
kf2 = keyframeArray[min(index, endIndex)]
kf3 = keyframeArray[min(index + 1, endIndex)]
den = (kf2[0] - kf1[0])
alpha = (nowTime - kf1[0]) / den if den != 0 else 0
return getCatmullromLerp(kf0, kf1, kf2, kf3, axis, alpha)
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
from ..exception.SafeEvalException import SafeEvalException
class SafeEval:
def __init__(self):
self.operators = {
'+': lambda x, y: x + y,
'-': lambda x, y: x - y,
'*': lambda x, y: x * y,
'/': lambda x, y: x / y if y != 0 else self.raiseException("发生了除以 0 错误")
}
def tokenize(self, expression):
"""将表达式分割成数字和运算符"""
tokens = []
number = ''
negative = False # 用于标记负号
for char in expression:
if char.isspace():
continue
elif char.isdigit() or char == '.':
number += char
elif char == '-':
if not number: # 如果前面没有数字,则表示负号
negative = True
else:
if number:
tokens.append(float(number) if not negative else -float(number))
number = ''
negative = False
tokens.append(char)
else:
if number:
tokens.append(float(number) if not negative else -float(number))
number = ''
negative = False
tokens.append(char)
if number:
tokens.append(float(number) if not negative else -float(number))
return tokens
def evaluate(self, expression):
"""计算表达式的值"""
tokens = self.tokenize(expression)
return self.parse_expression(tokens)
def parse_expression(self, tokens):
"""解析表达式"""
return self.parse_additive(tokens)
def parse_additive(self, tokens):
"""解析加法和减法"""
result = self.parse_multiplicative(tokens)
while tokens and tokens[0] in ('+', '-'):
op = tokens.pop(0)
if op == '+':
right = self.parse_multiplicative(tokens)
result = self.operators[op](result, right)
elif op == '-':
right = self.parse_multiplicative(tokens)
result = self.operators[op](result, right)
return result
def parse_multiplicative(self, tokens):
"""解析乘法和除法"""
result = self.parse_primary(tokens)
while tokens and tokens[0] in ('*', '/'):
op = tokens.pop(0)
if op == '*':
right = self.parse_primary(tokens)
result = self.operators[op](result, right)
elif op == '/':
right = self.parse_primary(tokens)
result = self.operators[op](result, right)
return result
def parse_primary(self, tokens):
"""解析基本表达式"""
if not tokens:
return None
token = tokens.pop(0)
if isinstance(token, float):
return token
elif token == '(':
result = self.parse_expression(tokens)
if not tokens or tokens[0] != ')':
return self.raiseException("括号不匹配")
tokens.pop(0) # Pop the closing parenthesis
return result
else:
return self.raiseException("无效的语法")
def raiseException(self, message):
"""抛出异常"""
SafeEvalException(message)
@@ -0,0 +1,68 @@
# coding=utf-8
from ..ToolsConfig import *
class ResultTimer(object):
"""
结果定时器, 在返回True时结束循环
"""
def __init__(self, func, delay=0.0, maxTimes=10, *args, **kwargs):
"""
:param func: 执行函数
:param delay: 执行延迟
:param maxTimes: 最大执行次数
"""
self.func = func
self.delay = delay
self.args = args
self.kwargs = kwargs
self.maxTimes = maxTimes
self.runTimes = 0
self.gameComp = serverCompFactory.CreateGame(LevelId) or clientCompFactory.CreateGame(LevelId)
def __timerFunc__(self):
result = self.func(*self.args, **self.kwargs)
if not result and self.runTimes < self.maxTimes: self.Start()
def Start(self):
"""
开始执行定时器
:return:
"""
self.gameComp.AddTimer(self.delay, self.__timerFunc__)
self.runTimes += 1
class TimesTimer(object):
"""
次数定时器
"""
def __init__(self, times, func, delay=0.0, *args, **kwargs):
"""
:param times: 循环次数
:param func: 执行函数
:param delay: 执行延迟
"""
self.func = func
self.delay = delay
self.times = times
self.args = args
self.kwargs = kwargs
self.gameComp = serverCompFactory.CreateGame(LevelId) or clientCompFactory.CreateGame(LevelId)
def __func(self, *args, **kwargs):
self.times -= 1
self.func(*args, **kwargs)
if self.times >= 0:
self.gameComp.AddTimer(self.delay, self.__func, *self.args, **self.kwargs)
def Start(self):
"""
开始执行定时器
:return:
"""
if self.times > 0:
self.times -= 1
self.gameComp.AddTimer(self.delay, self.__func, *self.args, **self.kwargs)