尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Cocos Creator实战教程:从UI到物理的完整Demo集合与性能优化

Cocos Creator实战教程:从UI到物理的完整Demo集合与性能优化
📅 发布时间:2026/7/12 13:26:15

1. 项目概述:为什么我们需要一个Cocos Creator教程Demo集合?

如果你正在学习或者已经使用Cocos Creator进行开发,大概率会遇到一个非常普遍的问题:官方文档虽然详尽,但面对一个具体功能点时,总感觉“隔了一层纱”。文档告诉你API怎么用,参数是什么,但一个完整的、可运行的、能直接看到效果的例子,往往比十页文档更有说服力。这就是“CocosCreator教程demo集合”这个项目存在的核心价值。

它不是一个简单的代码仓库堆砌,而是一个经过筛选、整理和重构的“实战案例库”。其目标非常明确:为不同阶段的Cocos Creator开发者提供一个“即查即用”的参考中心。无论是刚入门的新手想弄明白一个按钮点击事件怎么写,还是有一定经验的开发者想实现一个复杂的拖拽背包系统,都可以在这里找到对应的、可直接运行的Demo,看到最直观的实现逻辑和效果。这极大地降低了学习曲线和试错成本,让你能把精力更多地集中在游戏逻辑和创意实现上,而不是反复纠结于某个API的调用方式。

2. 集合的整体架构与设计思路

一个优秀的Demo集合,绝不是把网上能找到的代码片段胡乱丢在一起。它需要有清晰的脉络和实用的分类,让使用者能快速定位。基于多年的社区观察和自身开发经验,我认为一个理想的Cocos Creator教程Demo集合应该围绕以下几个核心维度进行组织。

2.1 按功能模块纵向切割

这是最直观、最常用的分类方式,直接对应引擎的核心子系统。开发者通常带着明确的功能目标而来,比如“我要做UI动画”或者“我需要物理碰撞”。集合应据此设立主要目录:

  • UI系统:这是游戏与玩家交互的窗口,需求最繁杂。Demo应覆盖基础控件(Button、Label、Slider、Toggle)的属性和事件绑定,复杂布局(Widget、Layout),以及各种动画效果(位置、缩放、旋转、颜色、序列帧动画)。一个高级的Demo可能会展示如何用代码动态创建UI列表,并实现滚动复用。
  • 动画系统:包括2D骨骼动画(DragonBones、Spine)的导入、状态控制和混合,以及3D骨骼动画的播放、剪辑切换和事件回调。还应有时间轴动画(Animation组件)的详细示例,展示如何控制多个属性的曲线变化。
  • 物理系统:区分2D(Box2D)和3D(Bullet/Cannon.js)。Demo需要展示刚体(RigidBody)的添加、物理材质(摩擦力、弹性)的设置、碰撞体(Collider)的形状(矩形、圆形、多边形、网格)定义,以及最重要的——碰撞和触发事件的脚本编写。一个“小球入盒”的Demo就能把整个流程串起来。
  • 音频系统:演示如何加载和播放背景音乐(循环)与音效(一次),并包含音量控制、暂停/继续等常见操作。对于Web平台,还需要注意音频的自动播放策略。
  • 资源管理与动态加载:这是性能优化的关键。Demo需要展示如何配置Bundle,以及如何使用resources.load或assetManager动态加载Prefab、SpriteFrame、AudioClip等,并在加载完成后进行实例化或使用。必须包含加载进度显示和错误处理的示例。
  • 渲染与图形:包括Sprite的裁剪、九宫格、网格渲染,Mask组件的使用,以及Shader的入门示例(如简单的颜色变换、流光效果)。对于3D,则包括材质、光源、相机的控制。

2.2 按实现复杂度横向分层

同一个功能,新手和高手需要的实现深度完全不同。集合应当体现这种差异:

  • 基础篇:目标是“跑通”。使用最简单直接的方式实现功能核心。例如,实现拖拽,基础篇可能就直接在onTouchMove中修改节点位置。代码简短,重点在于展示核心API的调用。
  • 进阶篇:目标是“用好”。考虑性能、可维护性和边界情况。同样是拖拽,进阶篇会引入拖拽限制区域、惯性滑动、与其他UI的遮挡关系处理,可能还会封装成一个可复用的DragDrop组件。
  • 综合/实战篇:目标是“串联”。将一个完整的游戏小模块作为Demo,如“无尽跑酷的角色控制”、“卡牌游戏的抽牌与手牌管理”、“RPG游戏的简易对话系统”。这类Demo最具参考价值,它展示了多个系统如何协同工作。

2.3 按学习路径阶段化引导

集合也可以服务于学习路径,帮助开发者循序渐进:

  • 引擎熟悉阶段:Demo集中于编辑器的使用——如何创建场景、管理节点树、配置组件属性、使用控制台。
  • 脚本编程阶段:Demo展示TypeScript脚本的基本结构、生命周期回调(onLoad,start,update)、常用模块(Vec3,Color,EventTarget)的使用。
  • 系统掌握阶段:对应上述功能模块的各个部分。
  • 项目实战阶段:提供小型完整项目的Demo,如“Flappy Bird”、“2048”,并附带关键设计思路的注释。

设计心得:在组织这类集合时,我强烈建议每个Demo都配备一个README.md文件。里面用一两句话说明这个Demo是干什么的,列出了关键的学习点和涉及的核心API,并给出1-2个思考题(例如:“如何让这个动画在播放完后自动销毁节点?”)。这能引导学习者从“复制粘贴”转向“理解与举一反三”。

3. 核心Demo解析与实操要点

下面,我将选取几个具有代表性的功能Demo,深入解析其实现细节和需要注意的“坑”。

3.1 UI动态列表与数据绑定

这是中大型项目中最常遇到的UI需求。一个粗糙的实现是在onLoad中循环实例化预制体,这在小规模时可行,但列表很长时会造成性能问题(大量节点渲染)和内存浪费。

进阶实现思路:

  1. 复用池(Recycle Pool):只创建可视区域及少量缓冲的列表项节点。当滚动时,将移出视口的节点回收,并用于填充新进入视口的数据位置,仅更新其显示内容。
  2. 数据与视图分离:维护一个数据数组。每个列表项预制体上挂载一个ItemRenderer组件,它提供一个updateView(data)方法。当某个节点被复用到新的数据索引时,就调用该方法传入新数据,更新UI显示。

关键代码片段示例:

// ItemRenderer.ts import { _decorator, Component, Label } from 'cc'; const { ccclass, property } = _decorator; @ccclass('ItemRenderer') export class ItemRenderer extends Component { @property(Label) public label: Label = null!; // 用于更新视图的公共方法 updateView(data: { id: number, name: string }) { this.label.string = `ID: ${data.id} - ${data.name}`; // 这里可以更新其他UI元素,如图标、按钮状态等 } }
// ListView.ts (简化版滚动复用逻辑) updateList(scrollView: ScrollView, data: any[]) { const content = scrollView.content; const viewHeight = scrollView.node.height; const itemHeight = 100; // 每个列表项的高度 // 计算可视区域的起始和结束索引 const startIndex = Math.floor(scrollView.getScrollOffset().y / itemHeight); const endIndex = Math.min(startIndex + Math.ceil(viewHeight / itemHeight) + 2, data.length); // 回收当前所有项 this.recycleAllItems(); // 为可视区域内的数据创建或复用项 for (let i = startIndex; i < endIndex; i++) { let itemNode = this.getItemFromPool(); itemNode.setPosition(0, -i * itemHeight, 0); // 设置位置 let renderer = itemNode.getComponent(ItemRenderer); renderer.updateView(data[i]); // 更新数据 content.addChild(itemNode); } }

注意事项:

  • 滚动视图(ScrollView)的Content节点最好使用Widget组件进行对齐,以确保正确计算尺寸。
  • 列表项高度固定时计算最简单。如果高度可变,需要更复杂的布局和计算,可以考虑使用引擎的Layout组件或第三方更成熟的列表组件。
  • 回收池的实现要小心内存泄漏,确保节点被正确地从父节点移除并放入池中。

3.2 2D物理碰撞与交互反馈

物理系统看似简单,但要让交互感觉“真实”和“可靠”,细节处理很重要。

一个完整的碰撞Demo应包含:

  1. 物理世界设置:在项目设置中启用物理引擎(2D物理),并设置重力等参数。
  2. 刚体与碰撞体:为需要参与物理模拟的节点添加RigidBody2D和Collider2D(如BoxCollider2D)。区分Static(静态,如地面)、Dynamic(动态,如玩家)、Kinematic(运动学,可由代码控制移动的物体)刚体类型。
  3. 碰撞分组:在项目设置中定义碰撞分组(如Player、Enemy、Ground、Item),并设置它们之间的碰撞矩阵。这能高效地控制哪些物体可以相互碰撞,避免不必要的计算。
  4. 脚本监听:在脚本中实现onBeginContact(碰撞开始)、onEndContact(碰撞结束)、onPreSolve(碰撞求解前,可修改碰撞属性)、onPostSolve(碰撞求解后,可获取冲量信息)等回调函数。

交互反馈示例(碰撞时播放音效并闪烁):

import { _decorator, Component, Collider2D, IPhysics2DContact, AudioSource, tween, Vec3 } from 'cc'; const { ccclass, property } = _decorator; @ccclass('PlayerController') export class PlayerController extends Component { @property(AudioSource) public hitSound: AudioSource = null!; start() { // 获取碰撞组件并注册回调 const collider = this.getComponent(Collider2D); if (collider) { collider.on('begin-contact', this.onBeginContact, this); } } onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { // 检查与谁发生了碰撞 if (otherCollider.group === 8) { // 假设8是“Enemy”分组 // 1. 播放受击音效 if (this.hitSound) { this.hitSound.play(); } // 2. 实现一个简单的闪烁效果(变红后恢复) tween(this.node) .to(0.1, { scale: new Vec3(1.2, 1.2, 1) }) // 轻微放大 .call(() => { const originalColor = this.node.getComponent(Sprite)?.color; // 这里可以通过修改Sprite颜色或使用材质来实现变色,简化处理: console.log('Hit by enemy!'); }) .to(0.1, { scale: Vec3.ONE }) // 恢复大小 .start(); } } }

实操要点:

  • 性能:物理引擎开销大,动态刚体数量不宜过多。对于大量静止的障碍物,使用静态刚体。对于仅需检测重叠而不需要物理反馈的(如触发区域),可以使用Trigger(触发器)。
  • 精度:连续碰撞检测(CCD)可以防止高速物体穿过另一个物体,但会消耗更多性能,酌情使用。
  • 同步:物理模拟在固定时间步进行,而渲染帧率可能变化。直接根据物理刚体的位置更新节点位置可能导致抖动。通常引擎会处理好这一步,但如果自己手动干预,需要注意同步问题。

3.3 资源动态加载与内存管理

这是决定项目是否流畅、是否闪退的关键。很多Demo只展示如何加载,却忘了展示如何释放。

标准的动态加载与释放流程:

import { _decorator, Component, resources, Prefab, Node, instantiate } from 'cc'; const { ccclass, property } = _decorator; @ccclass('AssetLoader') export class AssetLoader extends Component { private loadedPrefabs: Map<string, Prefab> = new Map(); // 缓存已加载的Prefab // 加载并实例化一个Prefab async loadAndCreatePrefab(url: string): Promise<Node> { let prefab: Prefab; // 先检查缓存 if (this.loadedPrefabs.has(url)) { prefab = this.loadedPrefabs.get(url)!; } else { // 动态加载 prefab = await new Promise<Prefab>((resolve, reject) => { resources.load(url, Prefab, (err, asset) => { if (err) { reject(err); return; } resolve(asset); }); }); this.loadedPrefabs.set(url, prefab); // 加入缓存 } // 实例化 const node = instantiate(prefab); this.node.addChild(node); return node; } // 释放一个不再使用的资源 releasePrefab(url: string) { if (this.loadedPrefabs.has(url)) { const prefab = this.loadedPrefabs.get(url)!; resources.release(url); // 释放资源 this.loadedPrefabs.delete(url); // 从缓存移除 console.log(`Released prefab: ${url}`); } } // 场景切换时,释放本场景特有的所有资源 onDestroy() { for (const url of this.loadedPrefabs.keys()) { resources.release(url); } this.loadedPrefabs.clear(); } }

内存管理核心原则:

  • 谁加载,谁负责(考虑释放):养成习惯,在加载资源时就想好它在什么生命周期后可以释放。
  • 善用缓存:对于频繁使用的资源(如UI图标、玩家预制体),加载一次后缓存起来,避免重复加载。
  • 理解引用计数:Cocos Creator使用引用计数管理资源。load会增加引用,release会减少引用。当引用为0时,资源才会被真正销毁。instantiate不会增加引用计数,但实例化出来的节点如果引用了其他资源(如图集),会持有那些资源的引用。
  • 使用Bundle:对于大型项目,一定要使用Asset Bundle将资源按模块划分。这样可以在进入不同模块时动态加载和释放整个Bundle,管理起来更清晰。

4. 实战:构建一个“简易技能系统”Demo

这个综合Demo将串联动画、事件、计时器和简单状态管理,模拟一个游戏角色释放技能的过程。

4.1 系统设计与组件划分

我们设计一个简单的技能,包含:前摇动画、伤害判定、技能特效、冷却计时。 创建以下节点和组件:

  • PlayerNode(角色节点)
    • PlayerController.ts:角色控制主逻辑,管理技能状态。
    • Sprite组件:显示角色精灵。
  • SkillButtonNode(UI按钮节点)
    • Button组件:用于触发技能。
    • SkillButton.ts:按钮控制脚本,处理点击、冷却显示(如填充图、倒计时文本)。
  • SkillEffectNode(技能特效节点,通常预制成Prefab)
    • Animation或ParticleSystem组件:播放技能特效。
    • SkillEffect.ts:控制特效播放和销毁。

4.2 核心逻辑实现

PlayerController.ts 部分代码:

const { ccclass, property } = _decorator; @ccclass('PlayerController') export class PlayerController extends Component { @property public skillCooldown: number = 3.0; // 技能冷却时间 private isSkillReady: boolean = true; private cooldownTimer: number = 0; update(deltaTime: number) { if (!this.isSkillReady) { this.cooldownTimer -= deltaTime; if (this.cooldownTimer <= 0) { this.isSkillReady = true; // 通知UI技能就绪 this.node.emit('skill-ready'); } } } // 由UI按钮调用 castSkill() { if (!this.isSkillReady) { console.log('Skill is cooling down!'); return; } // 1. 播放角色前摇动画 (这里假设有动画组件) // this.getComponent(Animation).play('attack'); // 2. 触发伤害判定(例如,延时执行,模拟前摇) this.scheduleOnce(() => { this.applySkillDamage(); }, 0.3); // 0.3秒后造成伤害 // 3. 生成技能特效 this.spawnSkillEffect(); // 4. 进入冷却 this.isSkillReady = false; this.cooldownTimer = this.skillCooldown; this.node.emit('skill-cast', this.skillCooldown); // 通知UI开始冷却 } private applySkillDamage() { // 这里实现伤害计算逻辑,例如射线检测前方的敌人 console.log('Skill damage applied!'); } private spawnSkillEffect() { resources.load('prefabs/SkillEffect', Prefab, (err, prefab) => { if (err) { console.error(err); return; } const effectNode = instantiate(prefab); effectNode.setPosition(this.node.position); // 在角色位置生成 this.node.parent.addChild(effectNode); // 添加到场景 // 特效播放完成后自动销毁 effectNode.getComponent(SkillEffect)?.playAndDestroy(); }); } }

SkillButton.ts 部分代码:

@ccclass('SkillButton') export class SkillButton extends Component { @property(Button) public button: Button = null!; @property(ProgressBar) public cooldownBar: ProgressBar = null!; // 冷却进度条 @property(Label) public cdLabel: Label = null!; // 冷却倒计时文本 private playerController: PlayerController = null!; start() { // 假设Player节点有tag或者通过其他方式查找 const playerNode = find('Player'); if (playerNode) { this.playerController = playerNode.getComponent(PlayerController); // 监听技能状态事件 playerNode.on('skill-cast', this.onSkillCast, this); playerNode.on('skill-ready', this.onSkillReady, this); } this.button.node.on(Button.EventType.CLICK, this.onButtonClick, this); this.updateButtonState(true); } onButtonClick() { if (this.playerController) { this.playerController.castSkill(); } } onSkillCast(cooldownTime: number) { this.updateButtonState(false); // 开始冷却UI更新 this.startCooldownUI(cooldownTime); } onSkillReady() { this.updateButtonState(true); } updateButtonState(isReady: boolean) { this.button.interactable = isReady; this.cooldownBar.node.active = !isReady; } startCooldownUI(totalTime: number) { let currentTime = totalTime; this.cdLabel.string = currentTime.toFixed(1); this.cooldownBar.progress = 1; // 使用schedule更新UI this.schedule(() => { currentTime -= 0.1; if (currentTime <= 0) { this.cdLabel.string = 'Ready'; this.cooldownBar.progress = 0; this.unschedule(this.updateCooldownUI); // 停止调度 return; } this.cdLabel.string = currentTime.toFixed(1); this.cooldownBar.progress = currentTime / totalTime; }, 0.1); // 每0.1秒更新一次 } }

这个Demo虽然简化,但涵盖了事件通信、异步处理、资源动态加载、UI状态同步和计时器等多个核心知识点,是一个非常好的小型综合练习。

5. 常见问题、调试技巧与性能优化备忘录

在实际开发中,你会遇到各种各样的问题。这里记录一些高频问题和我的排查经验。

5.1 高频问题速查表

问题现象可能原因排查步骤与解决方案
节点找不到(find 返回 null)1. 节点名字拼写错误。
2. 节点在查找时还未被激活(active=false)。
3. 在onLoad阶段查找,但目标节点是动态加载的,尚未加入场景树。
1. 检查拼写,注意大小写。
2. 确保节点active为true。
3. 将查找逻辑移到start中,或使用事件通知机制。动态加载的节点,在其onLoad或start中向外发送自定义事件。
资源加载失败1. 路径错误,未放在resources目录下或子路径不对。
2. 资源类型不匹配(如用SpriteFrame类型加载.prefab)。
3. 资源本身损坏或未导入成功。
1. 确认resources下的相对路径。assets/resources/xxx/yyy对应路径xxx/yyy。
2. 检查load调用时的类型参数。
3. 在编辑器资源管理器检查资源是否有红色报错图标。
物理碰撞不触发1. 碰撞体未正确添加或尺寸为0。
2. 碰撞分组未设置或矩阵未勾选。
3. 至少一方不是刚体,或刚体类型为StaticvsStatic(静态之间不产生碰撞)。
4. 节点scale为0或被父节点缩放影响。
1. 在场景编辑器中检查碰撞体绿色线框。
2. 检查项目设置中的物理碰撞矩阵。
3. 确保至少有一个是Dynamic或Kinematic刚体。
4. 检查节点及其所有父节点的缩放值。
动画播放异常1. 动画剪辑未关联到Animation组件。
2. 在播放未加载完成的动画。
3. 脚本中过早销毁了播放动画的节点。
1. 在Animation组件中检查Clips数组。
2. 使用animation.on(Animation.EventType.PLAY)监听播放开始事件。
3. 使用animation.on(Animation.EventType.FINISHED)监听播放结束再执行销毁。
在Web平台点击/触摸无效1. UI节点(如Button)被其他节点(如图片)遮挡,且该节点没有BlockInputEvents组件。
2. 节点active或interactable为false。
3. 节点scale或opacity为0。
1. 给可能遮挡UI的非交互节点添加BlockInputEvents组件,或调整渲染顺序。
2. 检查节点状态。
3. 检查节点的最终渲染属性。

5.2 调试技巧与开发者工具

  • 善用浏览器开发者工具:Cocos Creator构建的Web项目,可以在Chrome的Sources面板找到你的TypeScript源码(需开启SourceMap),直接断点调试。Console面板会输出引擎日志和你的console.log。
  • 使用Debug模式构建:在构建发布时,选择Debug模式,这会保留完整的日志和错误堆栈信息,便于定位问题。
  • 性能分析器(Profiler):Cocos Creator编辑器内置性能分析器。重点关注:
    • CPU:脚本逻辑(Update函数)是否耗时过长。
    • 渲染:Draw Call数量是否异常高。可通过合图(Auto Atlas)、动态图集、静态合批来降低。
    • 内存:检查Texture、Prefab等资源的缓存是否持续增长,警惕内存泄漏。
  • 自定义数据监控:对于游戏逻辑数据(如玩家血量、金币数、技能CD),可以创建一个全局的调试UI,实时显示这些变量的值,对于排查复杂的逻辑Bug非常有效。

5.3 性能优化要点清单

  1. Draw Call优化:
    • 静态合批:对于不会移动的、材质相同的静态物体(如背景图块),勾选Static属性,引擎会自动进行合批。
    • 动态合批:引擎会自动尝试合批少量、材质相同的动态Sprite。保持这些Sprite使用相同的SpriteFrame(来自同一图集)和材质。
    • 使用图集:将零碎的小图打包成图集,这是减少Draw Call最有效的手段之一。
  2. 节点数量优化:
    • 避免创建大量节点。对于重复元素(如子弹、特效),使用对象池(NodePool)。
    • 及时销毁不再使用的节点(node.destroy()),并从对象池中回收。
  3. 逻辑性能优化:
    • 减少update函数中的复杂计算和频繁的find、getComponent调用。必要时缓存组件引用。
    • 对于不需要每帧更新的逻辑,使用schedule或自定义计时器来控制执行频率。
    • 谨慎使用Mask组件,特别是嵌套Mask,它会打断合批并增加渲染开销。
  4. 内存优化:
    • 严格管理动态加载的资源,及时调用release释放。
    • 对于大型纹理,评估其尺寸是否必要,考虑使用压缩纹理格式(如Web平台的.webp,.astc)。
    • 在场景切换时,清理全局事件监听,避免内存泄漏。

构建和维护一个高质量的Cocos Creator教程Demo集合本身就是一个巨大的学习工程。它强迫你去深入理解每一个功能的细节、边界情况和最佳实践。我自己的习惯是,每学会一个新技巧或解决一个棘手的问题,就把它重构为一个清晰的、可独立运行的Demo,并加上详细的注释。久而久之,这个集合就成了我最宝贵的“外接大脑”和知识财富。当你需要向团队新人讲解某个模块,或者自己时隔半年后回头看某个功能时,这些Demo的价值就凸显无疑了。

相关新闻

  • IBM TPM2.0 模拟器 ibmtpm1682 安装:Ubuntu 22.04 避坑 3 个依赖与 OpenSSL 3.0 兼容
  • 华为海思QM队列管理机制:kae_driver中硬件资源调度的核心技术揭秘
  • Kettle 9.x 复杂作业流设计:3 步构建依赖驱动的并行执行引擎

最新新闻

  • 【预定SCI2区】基于凌日优化算法TSOA-BiTCN-BiGRU-Attention的风电预测算法研究Matlab实现
  • Qwen3-ASR-Toolkit与其他语音识别工具对比:为什么选择这个终极解决方案
  • 高一英语提分真相:单词语法做题三大瓶颈的90天突破路径
  • 技术博文选题规范:如何写出可复现、可验证的硬核内容
  • 如何将Spoilerwall集成到现有安全体系:与防火墙协同工作
  • 解锁Windows新玩法:APK Installer带你无缝安装Android应用

日新闻

  • IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结
  • Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动
  • SAP EPIC 建行回单查询:从标准类CL_EPIC_EXAMPLE_CN_CCB_GHTD到Z类的5处关键修改

周新闻

  • IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结
  • Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动
  • SAP EPIC 建行回单查询:从标准类CL_EPIC_EXAMPLE_CN_CCB_GHTD到Z类的5处关键修改

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号