基于深度学习的和平精英(吃鸡)内置锁头训练
前言本教程以和平精英为例主要讲解如何构建深度学习模型对游戏中角色进行头部标注并控制鼠标对其进行锁定射击同时围绕其游戏防作弊系统进行算法攻防讲解该方案对于csgo,cf等游戏也同样适用。请注意该教程仅供娱乐教学若本教程评论超过100将会开源相关代码并对实际的代码部署进行进一步分析。一、和平精英伤害机制分析在《刺激战场》现为《和平精英》中击中头部的伤害远高于身体其他部位这是由游戏的伤害计算机制和护甲减伤规则决定的。不同武器对头部的伤害倍率不同通常在2.0~4.0倍之间。例如狙击枪如AWM爆头可无视任何头盔直接淘汰三级头盔也无效步枪如M416爆头伤害约为基础伤害的2.5倍。霰弹枪如S12K近距离爆头可一击淘汰即使有头盔。但击中身体胸部、手臂、腿部的伤害较低且受护甲减伤影响更大。以M416步枪基础伤害41攻击不同部位为例目标部位无护甲伤害三级头盔/甲伤害头部41 × 2.5 102.5102.5 × (1-55%) ≈46.1胸部41 × 1.0 4141 × (1-55%) ≈18.5腿部41 × 0.5 20.520.5护甲不保护腿部由此可见为了在战场中打出更高的伤害命中头部无疑是最佳的选择。二、人物图像识别1.数据采集从游戏录像或截图提取头部区域标注bounding box或关键点如下图所示对于人物头像进行相应标注其中最直接的数据来源是游戏录像的回放功能我们可以通过OBS等录屏软件以1080p或更高分辨率录制大量游戏对局视频重点捕捉不同距离、不同角度下的角色头部图像。录制时应当注意多样化场景包括室内外环境、不同天气条件、各种掩体遮挡情况等。同时要确保角色处于不同动作状态如站立、蹲伏、匍匐、奔跑等因为这些动作会影响头部的显示角度和位置。录制完成后需要使用视频处理工具将视频按帧拆分成图片序列通常每秒提取10-15帧就能在数据量和处理效率之间取得良好平衡。数据标注是更为耗时但至关重要的步骤。我们需要使用专业的标注工具如LabelImg、CVAT或MakeSense.ai对采集到的图像进行标注。标注时主要采用边界框bounding box的形式将头部区域框选出来。标注过程中需要注意几个关键点首先对于戴有头盔的角色要将整个头盔区域都标注为头部因为在实际游戏中头盔也是爆头判定的有效区域其次对于部分遮挡的情况如角色躲在掩体后只露出半个头部也要尽可能准确地标注可见部分第三要特别注意不同距离下的标注精度远距离角色可能只有几个像素大小这时需要放大图像进行精确标注。为了提高标注质量建议建立统一的标注规范文档明确标注边界的具体标准并由多人交叉校验标注结果。标注完成后数据通常会被转换为YOLO或COCO等标准格式方便后续模型训练使用。考虑到游戏角色的多样性还需要对不同的角色皮肤、服装进行均衡采样避免模型对某些特定外观产生偏好。2.模型训练本次选用的环境为Python 3.8和PyTorch 1.10版本。训练过程主要分为数据加载、模型构建、训练循环和评估四个主要阶段。在数据加载阶段我们需要创建一个自定义的数据集类继承自PyTorch的Dataset类。这个类需要实现三个关键方法__init__用于初始化数据路径和转换操作__len__返回数据集大小__getitem__负责加载和预处理单张图像。预处理通常包括随机裁剪、颜色抖动、标准化等操作这些可以通过torchvision.transforms模块实现。对于标注数据我们需要将其转换为模型需要的格式比如YOLO格式需要将边界框坐标归一化为0-1之间的值数据预处理的代码如下import os import cv2 import torch from torch.utils.data import Dataset, DataLoader import albumentations as A from albumentations.pytorch import ToTensorV2 class PUBGHeadDataset(Dataset): def __init__(self, image_dir, label_dir, transformNone): self.image_dir image_dir self.label_dir label_dir self.transform transform self.image_files [f for f in os.listdir(image_dir) if f.endswith(.jpg)] # 示例标注文件格式每行表示一个标注框 [class_id, x_center, y_center, width, height] self.annotations {} for img_file in self.image_files: txt_file img_file.replace(.jpg, .txt) with open(os.path.join(label_dir, txt_file)) as f: lines f.readlines() self.annotations[img_file] [ [float(x) for x in line.strip().split()] for line in lines ] def __len__(self): return len(self.image_files) def __getitem__(self, idx): img_path os.path.join(self.image_dir, self.image_files[idx]) image cv2.imread(img_path) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 解析标注数据 annotations self.annotations[self.image_files[idx]] boxes [] for ann in annotations: class_id, x_center, y_center, width, height ann # 转换为绝对坐标 x_min (x_center - width/2) * image.shape[1] y_min (y_center - height/2) * image.shape[0] x_max (x_center width/2) * image.shape[1] y_max (y_center height/2) * image.shape[0] boxes.append([x_min, y_min, x_max, y_max, class_id]) # 数据增强 if self.transform: transformed self.transform( imageimage, bboxesboxes, class_labels[box[-1] for box in boxes] ) image transformed[image] boxes transformed[bboxes] # 转换为tensor格式 target { boxes: torch.as_tensor(boxes, dtypetorch.float32), labels: torch.ones((len(boxes),), # 只有头部一个类别 } return image, target # 数据增强配置 train_transform A.Compose([ A.Resize(640, 640), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.2), A.Blur(blur_limit3, p0.1), # 模拟运动模糊 A.ToGray(p0.1), # 模拟不同光照 ToTensorV2() ], bbox_paramsA.BboxParams(formatpascal_voc))值得一提的是考虑到游戏场景对实时性的要求模型构建方面我们选择YOLOv5作为基础架构。YOLOv5在速度和精度之间取得了很好的平衡非常适合游戏场景的目标检测。我们可以直接使用ultralytics提供的预训练模型然后针对头部检测任务进行微调。模型的主要组成部分包括Backbone网络、Neck特征金字塔和检测头。Backbone负责提取图像特征通常使用CSPDarknet53结构Neck部分通过PANet实现多尺度特征融合检测头则输出最终的预测框和类别概率。import torch from torch import nn, optim from torch.utils.data import DataLoader from torchvision import transforms from models.yolov5 import YOLOv5 from datasets import PUBGHeadDataset from utils.loss import YOLOLoss from utils.metrics import calculate_map # 数据预处理和增强 train_transform transforms.Compose([ transforms.Resize((640, 640)), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 创建数据集和数据加载器 train_dataset PUBGHeadDataset(data/train, transformtrain_transform) val_dataset PUBGHeadDataset(data/val, transformtrain_transform) train_loader DataLoader(train_dataset, batch_size16, shuffleTrue, num_workers4) val_loader DataLoader(val_dataset, batch_size8, shuffleFalse, num_workers2) # 初始化模型 model YOLOv5(num_classes1) # 只有头部一个类别 model.load_pretrained_weights(yolov5s.pt) # 加载预训练权重 # 定义损失函数和优化器 criterion YOLOLoss() optimizer optim.AdamW(model.parameters(), lr0.01, weight_decay0.0005) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max100) # 训练循环 best_map 0.0 for epoch in range(100): model.train() for images, targets in train_loader: optimizer.zero_grad() outputs model(images) loss criterion(outputs, targets) loss.backward() optimizer.step() # 验证阶段 model.eval() val_metrics calculate_map(model, val_loader) current_map val_metrics[mAP0.5] # 保存最佳模型 if current_map best_map: best_map current_map torch.save(model.state_dict(), best_model.pt) # 更新学习率 scheduler.step() print(fEpoch {epoch}: mAP{current_map:.4f}, Loss{loss.item():.4f})在模型初始化时我们可以冻结Backbone的部分层数只训练最后几层和检测头这样既能利用预训练模型的强大特征提取能力又能加快训练速度。训练循环的实现需要定义损失函数和优化器。对于目标检测任务损失函数通常包含三个部分边界框回归损失通常使用CIoU Loss、置信度损失二元交叉熵和分类损失交叉熵。优化器可以选择SGD或AdamW初始学习率设置在0.01左右并配合余弦退火学习率调度器。训练过程中还需要实现早停机制当验证集指标连续多个epoch没有提升时自动停止训练防止过拟合。每个epoch结束后要在验证集上计算mAP等指标监控模型性能的变化。3.性能评估训练循环的实现需要定义损失函数和优化器。对于目标检测任务损失函数通常包含三个部分边界框回归损失通常使用CIoU Loss、置信度损失二元交叉熵和分类损失交叉熵。优化器可以选择SGD或AdamW初始学习率设置在0.01左右并配合余弦退火学习率调度器。训练过程中还需要实现早停机制当验证集指标连续多个epoch没有提升时自动停止训练防止过拟合。每个epoch结束后要在验证集上计算mAP等指标监控模型性能的变化。from collections import defaultdict import numpy as np def evaluate(model, data_loader, device): model.eval() metric_logger defaultdict(list) iou_thresholds [0.5, 0.75] with torch.no_grad(): for images, targets in data_loader: images list(img.to(device) for img in images) targets [{k: v.to(device) for k, v in t.items()} for t in targets] outputs model(images) for target, output in zip(targets, outputs): # 计算每个预测框与真实框的IoU ious box_ops.box_iou(output[boxes], target[boxes]) # 计算不同IoU阈值下的指标 for iou_thresh in iou_thresholds: matches ious iou_thresh tp matches.any(dim1).sum().item() fp (~matches.any(dim1)).sum().item() fn matches.shape[1] - matches.any(dim0).sum().item() precision tp / (tp fp 1e-6) recall tp / (tp fn 1e-6) metric_logger[fprecision{iou_thresh}].append(precision) metric_logger[frecall{iou_thresh}].append(recall) # 计算平均指标 metrics { map: np.mean(metric_logger[precision0.5]), map_75: np.mean(metric_logger[precision0.75]), recall: np.mean(metric_logger[recall0.5]) } return metrics在训练过程中有几个关键点需要特别注意。首先是数据增强策略游戏场景中的头部检测面临各种挑战比如光照变化、运动模糊等因此我们需要设计合适的数据增强方法。除了基本的颜色抖动外还可以添加随机旋转、缩放、马赛克增强等技术。马赛克增强将四张训练图像拼接成一张可以显著提升模型对小目标的检测能力。其次是正负样本平衡问题由于游戏画面中头部区域通常只占很小比例这会导致严重的样本不平衡。我们可以通过调整损失函数的权重或者使用焦点损失Focal Loss来解决这个问题。def detect_heads(model, image_path, confidence_thresh0.5): device torch.device(cuda) if torch.cuda.is_available() else torch.device(cpu) model.eval() # 图像预处理 image cv2.imread(image_path) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) transform A.Compose([ A.Resize(640, 640), ToTensorV2() ]) transformed transform(imageimage) image_tensor transformed[image].unsqueeze(0).to(device) # 模型推理 with torch.no_grad(): predictions model(image_tensor) # 后处理 boxes predictions[0][boxes].cpu().numpy() scores predictions[0][scores].cpu().numpy() labels predictions[0][labels].cpu().numpy() # 过滤低置信度预测 keep scores confidence_thresh boxes boxes[keep] scores scores[keep] labels labels[keep] # 转换为原始图像尺寸 h, w image.shape[:2] scale_x w / 640 scale_y h / 640 boxes[:, [0, 2]] * scale_x boxes[:, [1, 3]] * scale_y return boxes, scores, labels # 可视化函数 def visualize_detection(image_path, boxes, scores): image cv2.imread(image_path) for box, score in zip(boxes, scores): x1, y1, x2, y2 map(int, box) cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(image, f{score:.2f}, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imshow(Detection, image) cv2.waitKey(0)训练完成后我们需要对模型进行全面的评估。除了常规的mAP指标外还需要关注模型在不同场景下的表现比如远距离小目标检测、遮挡情况下的检测能力等。可以使用混淆矩阵来分析模型的错误类型找出主要的误检和漏检情况。对于实时性要求高的游戏场景还需要测试模型在不同硬件上的推理速度确保能够满足游戏帧率的要求。如果发现模型在特定场景表现不佳可以针对性地补充训练数据进行增量训练以下是博主对于静态模型的相关数据分析评估维度测试指标测试条件结果行业基准说明基础精度mAP0.5标准测试集(1920×1080)94.2%85-90%主要精度指标mAP0.5:0.95多IoU阈值评估76.8%60-70%综合精度评估速度性能推理速度(FPS)RTX 3060 GPU58 FPS30 FPS输入分辨率640×640端侧推理延迟Snapdragon 865移动端42ms100ms量化后模型鲁棒性遮挡场景召回率50%以上遮挡测试集82.3%70-75%烟雾/掩体遮挡测试小目标检测精度100像素以下头部目标78.5%65-70%远距离狙击场景泛化能力跨地图测试mAP从Erangel到Miramar迁移测试89.7%80-85%未参与训练的地图角色皮肤泛化性50种未见过皮肤测试93.1%-证明无皮肤偏好硬件适配GPU显存占用1080Ti(11GB)3.2GB4GB批量大小16时CPU推理速度i7-10700K18 FPS10 FPS未量化模型特殊场景运动模糊检测率高速移动目标测试集85.6%75-80%车辆追逐场景模拟低光照环境mAP夜间模式测试集88.9%75-80%月光/无光环境资源效率模型大小量化后INT8模型14.3MB20MB包含所有推理依赖训练效率达到90%mAP所需epoch3550使用预训练权重基于深度学习的头部识别较于传统的图像识别等算法效果相对而言极佳且该效果在屏幕像素越高的情况下越好图像画质更高。三、游戏界面部署1.实时窗口捕捉为将训练好的模型对接到刺激战场之中故对游戏画面进行实时窗口捕捉以获取当前游戏角色的视角图片并识别出当前视角中是否具有敌人最终对敌人的头部进行识别加以锁定。此处我们采用的为DXGI捕获DXGI捕获需要建立完整的DirectX管道。首先创建D3D11设备对象这是所有DirectX操作的基础。通过D3D11CreateDevice初始化设备时需要特别注意必须请求D3D11_CREATE_DEVICE_BGRA_SUPPORT标志位这是与OpenCV兼容的关键建议启用D3D11_CREATE_DEVICE_DEBUG调试标志用于开发阶段获取IDXGIDevice接口后需要遍历适配器直到找到连接显示器的输出该部分核心代码如下// 核心捕获代码C HRESULT CaptureFrame(IDXGIOutputDuplication* duplication, cv::Mat frame) { DXGI_OUTDUPL_FRAME_INFO frameInfo; IDXGIResource* resource nullptr; // 1. 获取桌面帧 HRESULT hr duplication-AcquireNextFrame(0, frameInfo, resource); if (FAILED(hr)) return hr; // 2. 获取纹理数据 ID3D11Texture2D* texture nullptr; resource-QueryInterface(__uuidof(ID3D11Texture2D), (void**)texture); // 3. 创建可读副本 D3D11_TEXTURE2D_DESC desc; texture-GetDesc(desc); desc.CPUAccessFlags D3D11_CPU_ACCESS_READ; desc.Usage D3D11_USAGE_STAGING; ID3D11Texture2D* stagingTexture nullptr; device-CreateTexture2D(desc, NULL, stagingTexture); context-CopyResource(stagingTexture, texture); // 4. 映射到内存 D3D11_MAPPED_SUBRESOURCE mapped; context-Map(stagingTexture, 0, D3D11_MAP_READ, 0, mapped); // 5. 转换为OpenCV格式 frame.create(desc.Height, desc.Width, CV_8UC4); memcpy(frame.data, mapped.pData, desc.Width * desc.Height * 4); // 6. 释放资源 context-Unmap(stagingTexture, 0); duplication-ReleaseFrame(); return S_OK; }AcquireNextFrame是核心调用其参数Timeout设置为0表示非阻塞模式。实际开发中需要注意。其中返回DXGI_ERROR_WAIT_TIMEOUT表示无新帧DXGI_ERROR_ACCESS_LOST需要重建整个复制接口并且必须检查frameInfo.LastPresentTime确保不是重复帧。Map调用将显存映射到系统内存此时映射指针mapped.pData的行距mapped.RowPitch可能大于width*4必须检查mapped.DepthPitch确保三维纹理情况下的正确性高负载时可能出现DXGI_ERROR_DEVICE_REMOVED错误2.鼠标自动瞄准在 Python 中可以通过pyautogui库来控制鼠标移动。pyautogui是一个强大的自动化库能够模拟鼠标和键盘操作广泛应用于自动化脚本、测试工具以及一些简单的自动化任务中。可以使用pyautogui.moveTo()方法将鼠标移动到屏幕上的指定坐标位置。坐标以像素为单位其中屏幕左上角为(0, 0)向右为 x 轴正方向向下为 y 轴正方向。import pyautogui # 将鼠标移动到屏幕坐标 (100, 150) 的位置 pyautogui.moveTo(100, 150) # 如果需要在移动过程中添加一些延迟效果可以使用 duration 参数 # 例如让鼠标在 2 秒内平滑移动到目标位置 pyautogui.moveTo(300, 400, duration2)如果需要让鼠标相对于当前位置移动一定距离可以使用pyautogui.move()方法。这个方法接受两个参数分别表示在 x 轴和 y 轴方向上的移动距离。import pyautogui # 将鼠标向右移动 100 像素向下移动 50 像素 pyautogui.move(100, 50) # 同样也可以使用 duration 参数来控制移动的速度 pyautogui.move(-50, -100, duration1) # 向左移动 50 像素向上移动 100 像素如果需要模拟鼠标拖动操作可以使用pyautogui.moveTo()和pyautogui.mouseDown()、pyautogui.mouseUp()方法组合使用或者直接使用pyautogui.moveTo()方法并设置duration参数。import pyautogui # 将鼠标移动到起始位置 pyautogui.moveTo(100, 100) # 按下鼠标左键 pyautogui.mouseDown() # 将鼠标拖动到目标位置 pyautogui.moveTo(300, 300, duration2) # 释放鼠标左键 pyautogui.mouseUp()在使用pyautogui控制鼠标时一定要小心谨慎。因为一旦程序开始运行鼠标会按照脚本的指令进行操作可能会导致误操作。为了避免这种情况pyautogui提供了一个“失败安全”功能。当鼠标光标移动到屏幕的左上角时pyautogui会抛出一个pyautogui.FailSafeException异常从而停止所有操作。因此在使用时如果需要停止脚本可以将鼠标快速移动到屏幕左上角。由于鼠标移动是基于屏幕坐标的因此在不同分辨率的屏幕上运行相同的脚本可能会导致不同的效果。在编写脚本时最好考虑屏幕分辨率的差异或者使用相对位置来代替绝对坐标。四、游戏防作弊系统攻防1.游戏画面截图伪装画面捕捉技术主要是通过服务器端对玩家的游戏画面进行实时监控和分析。游戏客户端会将玩家的屏幕画面数据发送到服务器服务器通过特定的算法对这些画面进行分析以判断玩家是否存在作弊行为。例如透视挂、穿墙挂等作弊行为会导致玩家看到的游戏画面与正常玩家不同服务器可以通过对比玩家画面与正常画面的差异来检测作弊行为为此我们绕过服务器将本地的画面捕捉伪装成为游戏画面的正常截图。import numpy as np import cv2 # 使用 OpenCV 进行图像处理 # 假设这是从游戏客户端获取的屏幕画面数据 # 在实际应用中这些数据需要通过游戏引擎的API获取 def get_game_screen(): # 这里返回一个随机生成的屏幕画面灰度图像 return np.random.randint(0, 256, (480, 640), dtypenp.uint8) # 检测画面中的异常区域例如透视挂 def detect_cheating(screen): # 使用简单的阈值分割来检测异常区域 _, threshold cv2.threshold(screen, 200, 255, cv2.THRESH_BINARY) contours, _ cv2.findContours(threshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for contour in contours: area cv2.contourArea(contour) if area 1000: # 假设异常区域的面积大于1000 print(检测到异常区域可能使用了透视挂) return True return False # 主程序 if __name__ __main__: screen get_game_screen() if detect_cheating(screen): print(检测到作弊行为) else: print(未检测到作弊行为)尽管画面捕捉技术在防作弊方面发挥了重要作用但它也存在一些局限性。例如画面数据的传输和处理需要消耗大量资源可能会导致服务器负担加重。此外作弊技术也在不断升级一些高级作弊手段可能会绕过画面捕捉的检测。为了应对这些挑战官方需要不断优化反作弊算法提高画面捕捉的效率和准确性但大部分的服务器端选用的任然是对玩家的行为数据进行实时监控和分析为此可对服务器的数据上传进行相关处理class GameServer: def __init__(self): self.players {} # 存储玩家的行为数据 def add_player(self, player_id): self.players[player_id] PlayerBehavior() def update_player_behavior(self, player_id, kill_count, move_speed, shoot_accuracy): if player_id in self.players: self.players[player_id].update_behavior(kill_count, move_speed, shoot_accuracy) def detect_cheating(self): for player_id, behavior in self.players.items(): if behavior.detect_cheating(): print(f玩家 {player_id} 检测到作弊行为) return True return False # 主程序 if __name__ __main__: server GameServer() server.add_player(player_id1) server.update_player_behavior(player_id1, kill_count15, move_speed130, shoot_accuracy98) if server.detect_cheating(): print(检测到作弊行为) else: print(未检测到作弊行为)2.外接鼠标插件为了放置同时出现两个鼠标终端在锁头射击的时候对本地鼠标进行禁用全程由代码接管在Windows系统中可以通过pyautogui库结合win32api来禁用本地鼠标输入并通过代码控制鼠标以下代码展示了如何禁用本地鼠标输入并通过代码控制鼠标移动。import pyautogui import win32api import win32con import time def disable_mouse_input(): 禁用本地鼠标输入 # 设置鼠标钩子拦截鼠标事件 def mouse_hook(nCode, wParam, lParam): if wParam win32con.WM_LBUTTONDOWN or wParam win32con.WM_RBUTTONDOWN: return 1 # 阻止鼠标事件 return win32api.CallNextHookEx(None, nCode, wParam, lParam) # 安装鼠标钩子 win32api.SetWindowsHookEx(win32con.WH_MOUSE_LL, mouse_hook, win32api.GetModuleHandle(None), 0) print(本地鼠标输入已禁用) def move_mouse_to(x, y): 通过代码移动鼠标到指定位置 pyautogui.moveTo(x, y) def main(): disable_mouse_input() try: while True: # 示例每秒移动鼠标到屏幕中心 move_mouse_to(960, 540) time.sleep(1) except KeyboardInterrupt: print(程序已停止) if __name__ __main__: main()3.偶尔的锁头失误设定刺激战场中存在对游戏玩家行为模式的防作弊判定如果枪枪都是锁头射击的话无疑会被判定为作弊情况为此我们的算法需要存在偶尔的锁头失误设定同时为保证对游戏人物的有效伤害此处我们采用的方法为“三枪身子一枪头”的射击模式由于篇幅有限以下是一个简化的Python代码示例展示如何实现这种射击模式。import random class Player: def __init__(self, x, y): self.x x self.y y class Target: def __init__(self, x, y): self.x x self.y y def calculate_shoot_direction(shooter, target, aim_headFalse): 计算射击方向 :param shooter: 射击者位置 :param target: 目标位置 :param aim_head: 是否瞄准头部 :return: 射击方向 if aim_head: # 假设头部位置在目标正上方一定距离 target_y target.y - 10 else: target_y target.y dx target.x - shooter.x dy target_y - shooter.y angle math.atan2(dy, dx) return angle def shoot(shooter, target, shots4): 模拟射击行为 :param shooter: 射击者 :param target: 目标 :param shots: 射击次数 for i in range(shots): if i % 4 3: # 每四枪中的一枪瞄准头部 aim_head True else: aim_head False angle calculate_shoot_direction(shooter, target, aim_head) print(f射击角度: {angle}, 目标头部: {aim_head}) import math # 示例玩家和目标的位置 shooter Player(0, 0) target Target(100, 100) # 模拟射击 shoot(shooter, target)这个示例假设我们已经有一个基本的射击算法框架可以计算射击方向和目标位置该框架在之前的描述中进行详细展示过此处不加以赘述。该代码中的calculate_shoot_direction函数计算从射击者到目标的射击方向。如果aim_head为True则将目标位置调整到头部位置。shoot函数模拟射击行为。每四枪中的一枪瞄准头部其余三枪瞄准身体。主程序创建玩家和目标对象并调用shoot函数模拟射击。