在这里插入图片描述

案例开源地址:https://atomgit.com/nutpi/rn_openharmony_lol

符文配置器让用户可以自由搭配符文,但对于新手来说,可能不知道什么搭配是好的。符文预设功能提供一些常用的、经过验证的符文搭配,让用户可以快速参考或直接使用。

这篇文章我们来实现符文预设页面,重点是静态数据的组织方式、卡片列表的展示、以及如何设计易于维护的预设数据结构。

为什么需要符文预设

在英雄联盟中,符文系统有 5 个系别,每个系别有 4 层符文,加上副系的选择,组合数量非常庞大。对于新手玩家来说,面对这么多选择往往不知所措。

符文预设的价值在于:

  1. 降低学习成本:新手不需要理解每个符文的效果,直接使用推荐配置
  2. 提供参考基准:即使是老玩家,也可以参考预设来优化自己的配置
  3. 快速开始游戏:不需要每次都从头配置,选择预设即可

从产品设计的角度,预设功能是"专家系统"的一种体现——把专家的知识固化下来,让普通用户也能享受到专业级的配置。

预设数据的设计

符文预设是相对静态的数据,不需要从 API 获取。我们直接在代码中定义:

const presets = [
  {name: '电刑法师', primary: '主宰', secondary: '巫术', desc: '适合爆发型法师'},
  {name: '征服者战士', primary: '精密', secondary: '坚决', desc: '适合持续作战的战士'},
  {name: '彗星消耗', primary: '巫术', secondary: '启迪', desc: '适合消耗型法师'},
  {name: '不灭之握', primary: '坚决', secondary: '精密', desc: '适合坦克英雄'},
  {name: '致命节奏', primary: '精密', secondary: '主宰', desc: '适合攻速型射手'},
];

数据结构说明

  • name:预设的名称,通常以基石符文命名
  • primary:主系名称
  • secondary:副系名称
  • desc:简短的描述,说明适合什么类型的英雄

为什么用中文而不是 key?

这里直接用中文名称而不是英文 key(如 “Domination”),是因为这个数据只用于展示,不需要做映射转换。如果后续要和符文配置器联动(比如点击预设自动填充配置),可以改成用 key。

预设数据的来源与游戏理解

这些预设是根据游戏经验总结的常用搭配,每个预设都有其适用场景:

电刑法师:主宰系的电刑是爆发型法师的首选基石,配合巫术系的法力流系带和焦灼,能最大化爆发伤害。典型英雄包括阿狸、辛德拉、维克托等。电刑的触发条件是 3 秒内用 3 个独立技能或攻击命中敌人,对于技能连招流畅的法师来说非常容易触发。

征服者战士:精密系的征服者适合需要持续作战的战士,叠满后提供大量攻击力和治疗。配合坚决系的骨甲和复苏之风,增加生存能力。典型英雄包括剑姬、青钢影、锐雯等。征服者需要叠层才能发挥最大效果,所以适合能持续输出的英雄。

彗星消耗:巫术系的奥术彗星适合远程消耗型法师,配合启迪系的饼干和时间扭曲补剂,增强对线能力。典型英雄包括泽拉斯、拉克丝、维迦等。彗星的优势是冷却时间短,配合减速技能几乎必中。

不灭之握:坚决系的不灭之握是坦克的标配,提供持续的治疗和生命值加成。配合精密系的凯旋和韧性,增强团战能力。典型英雄包括奥恩、塞恩、慎等。不灭之握每 4 秒可以触发一次,对于需要频繁换血的坦克来说收益很高。

致命节奏:精密系的致命节奏适合攻速型射手,战斗中不断提升攻速。配合主宰系的血之滋味和贪欲猎手,增加续航。典型英雄包括金克丝、薇恩、卡莎等。致命节奏在长时间战斗中收益递增,适合后期团战。

页面组件实现

import React from 'react';
import {View, Text, ScrollView, StyleSheet} from 'react-native';
import {colors} from '../../styles/colors';

export function RunePresetPage() {
  return (
    <ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
      <Text style={styles.title}>常用符文预设</Text>
      <Text style={styles.subtitle}>快速选择适合的符文搭配</Text>
      {presets.map((preset, index) => (
        <View key={index} style={styles.presetCard}>
          <Text style={styles.presetName}>{preset.name}</Text>
          <View style={styles.pathRow}>
            <Text style={styles.pathText}>主系: {preset.primary}</Text>
            <Text style={styles.pathText}>副系: {preset.secondary}</Text>
          </View>
          <Text style={styles.presetDesc}>{preset.desc}</Text>
        </View>
      ))}
      <View style={styles.bottomSpace} />
    </ScrollView>
  );
}

页面结构分析

整个页面由三部分组成:标题区域、预设卡片列表、底部留白。这种结构简洁明了,用户一眼就能理解页面的用途。

为什么用 index 作为 key?

预设数据是静态的,不会动态增删或重排序,用 index 作为 key 是安全的。React 的 key 主要用于优化列表渲染,当列表项的顺序或数量变化时,key 帮助 React 识别哪些项需要更新。对于静态列表,index 完全够用。

如果预设数据会变化(比如用户可以添加自定义预设),应该给每个预设加一个唯一 ID:

const presets = [
  {id: 'electrocute-mage', name: '电刑法师', ...},
  {id: 'conqueror-fighter', name: '征服者战士', ...},
];

卡片内容的组织

每张预设卡片包含三部分信息,形成清晰的视觉层次:

<View key={index} style={styles.presetCard}>
  <Text style={styles.presetName}>{preset.name}</Text>
  <View style={styles.pathRow}>
    <Text style={styles.pathText}>主系: {preset.primary}</Text>
    <Text style={styles.pathText}>副系: {preset.secondary}</Text>
  </View>
  <Text style={styles.presetDesc}>{preset.desc}</Text>
</View>

预设名称:用金色大字体,是卡片最醒目的部分。名称通常是基石符文的名字,玩家一看就知道是什么流派。金色在游戏中代表稀有和重要,用在这里能吸引用户注意。

主副系信息:横向排列,显示主系和副系的名称。这是符文搭配的核心信息,用户通过这两个信息就能大致了解这个预设的风格。

描述文字:用较小的灰色字体,说明这个预设适合什么类型的英雄。帮助用户判断是否适合自己当前要玩的英雄。

样式设计详解

const styles = StyleSheet.create({
  container: {
    flex: 1, 
    backgroundColor: colors.background, 
    padding: 16
  },
  title: {
    fontSize: 20, 
    fontWeight: 'bold', 
    color: colors.textPrimary, 
    marginBottom: 4
  },
  subtitle: {
    fontSize: 14, 
    color: colors.textSecondary, 
    marginBottom: 20
  },
  presetCard: {
    backgroundColor: colors.backgroundCard, 
    borderRadius: 12, 
    padding: 16, 
    marginBottom: 12, 
    borderWidth: 1, 
    borderColor: colors.border
  },
  presetName: {
    fontSize: 18, 
    fontWeight: '600', 
    color: colors.textGold, 
    marginBottom: 8
  },
  pathRow: {
    flexDirection: 'row', 
    marginBottom: 8
  },
  pathText: {
    fontSize: 14, 
    color: colors.textPrimary, 
    marginRight: 16
  },
  presetDesc: {
    fontSize: 13, 
    color: colors.textSecondary
  },
  bottomSpace: {
    height: 20
  },
});

容器样式

flex: 1 让容器占满可用空间,padding: 16 给内容留出呼吸空间。这个 padding 值在整个 App 中保持一致,形成统一的视觉节奏。

标题样式

标题和副标题形成主次关系。标题 20px 加粗,副标题 14px 普通,颜色也有深浅之分。marginBottom: 20 让标题区域和卡片列表之间有足够的间距。

卡片样式

  • borderRadius: 12:圆角让卡片更柔和,和整个 App 的设计语言一致
  • padding: 16:内边距让内容不会贴着边缘,阅读更舒适
  • marginBottom: 12:卡片之间的间距,不会太挤也不会太散
  • borderWidth: 1 + borderColor: colors.border:细边框增加卡片的层次感

预设名称的颜色

colors.textGold 金色,和游戏中的风格一致。金色在深色背景上非常醒目,能有效吸引用户注意力。

主副系信息的布局

flexDirection: 'row' 让主系和副系横向排列,marginRight: 16 在它们之间留出间距。这种布局紧凑但不拥挤,信息一目了然。

静态数据 vs 动态数据

这个页面使用静态数据,这是一个有意的设计选择。我们来分析一下静态数据和动态数据的取舍:

静态数据的优势

  1. 无需网络请求:页面打开即可显示,没有加载等待
  2. 离线可用:即使没有网络也能正常使用
  3. 代码简单:不需要处理加载状态、错误状态
  4. 性能好:数据直接在内存中,访问速度快

静态数据的劣势

  1. 更新需要发版:如果要修改预设,需要发布新版本
  2. 无法个性化:所有用户看到的都是一样的预设
  3. 数据量有限:不适合大量数据

对于符文预设这个场景,静态数据是合适的选择。预设数量不多,更新频率低,而且用户期望的是"专家推荐",不需要个性化。

如果后续要支持更多预设或者个性化推荐,可以改成从服务器获取:

const [presets, setPresets] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
  async function loadPresets() {
    const data = await api.getRunePresets();
    setPresets(data);
    setLoading(false);
  }
  loadPresets();
}, []);

交互增强:添加点击反馈

当前实现是纯展示,用户只能看不能操作。我们可以添加点击功能,让用户点击预设后跳转到符文配置器:

import {TouchableOpacity} from 'react-native';
import {useNavigation} from '../../context/NavigationContext';

export function RunePresetPage() {
  const {navigate} = useNavigation();
  
  const handlePresetPress = (preset) => {
    // 跳转到符文配置器,并传递预设数据
    navigate('RuneBuilder', {preset});
  };
  
  return (
    <ScrollView style={styles.container}>
      {presets.map((preset, index) => (
        <TouchableOpacity 
          key={index} 
          style={styles.presetCard}
          onPress={() => handlePresetPress(preset)}
          activeOpacity={0.7}
        >
          {/* 卡片内容 */}
        </TouchableOpacity>
      ))}
    </ScrollView>
  );
}

activeOpacity={0.7} 让用户点击时有视觉反馈,透明度变化表示"我点到了"。

扩展数据结构

如果要支持点击应用预设的功能,需要扩展数据结构,包含具体的符文选择:

const presets = [
  {
    id: 'electrocute-mage',
    name: '电刑法师',
    primary: 'Domination',
    secondary: 'Sorcery',
    primaryRunes: {
      keystone: 'Electrocute',
      slot1: 'TasteOfBlood',
      slot2: 'EyeballCollection',
      slot3: 'RavenousHunter',
    },
    secondaryRunes: {
      slot1: 'ManaflowBand',
      slot2: 'Scorch',
    },
    statMods: ['adaptive', 'adaptive', 'armor'],
    desc: '适合爆发型法师',
    champions: ['阿狸', '辛德拉', '维克托'],
  },
  // ...
];

这个扩展结构包含:

  • id:唯一标识,用于 key 和数据关联
  • primaryRunes / secondaryRunes:具体的符文选择
  • statMods:属性碎片的选择
  • champions:推荐使用这个预设的英雄

按英雄筛选预设

有了 champions 字段,就可以实现按英雄筛选预设的功能:

const [selectedChampion, setSelectedChampion] = useState(null);

const filteredPresets = selectedChampion
  ? presets.filter(p => p.champions.includes(selectedChampion))
  : presets;

return (
  <ScrollView>
    {/* 英雄选择器 */}
    <ChampionPicker 
      value={selectedChampion} 
      onChange={setSelectedChampion} 
    />
    
    {/* 筛选后的预设列表 */}
    {filteredPresets.map(preset => (
      <PresetCard key={preset.id} preset={preset} />
    ))}
  </ScrollView>
);

这样用户可以先选择要玩的英雄,然后看到适合这个英雄的符文预设。

用户自定义预设

更进一步,可以让用户保存自己的符文配置为预设:

const [customPresets, setCustomPresets] = useState([]);

// 从 AsyncStorage 加载用户预设
useEffect(() => {
  async function loadCustomPresets() {
    const saved = await AsyncStorage.getItem('customRunePresets');
    if (saved) {
      setCustomPresets(JSON.parse(saved));
    }
  }
  loadCustomPresets();
}, []);

// 保存新预设
const savePreset = async (config, name) => {
  const newPreset = {
    id: Date.now().toString(),
    name,
    ...config,
    isCustom: true,
  };
  const updated = [...customPresets, newPreset];
  setCustomPresets(updated);
  await AsyncStorage.setItem('customRunePresets', JSON.stringify(updated));
};

// 合并系统预设和用户预设
const allPresets = [...presets, ...customPresets];

用户预设用 isCustom: true 标记,在 UI 上可以显示不同的样式或者提供删除功能。

小结

符文预设页面虽然代码量不大,但涉及到几个重要的设计决策:

  1. 静态数据的选择:对于更新频率低、数量少的数据,静态定义比动态获取更合适
  2. 数据结构的设计:当前结构简单够用,但预留了扩展空间
  3. 视觉层次的处理:通过字体大小、颜色深浅、间距等手段建立信息层级
  4. 交互的渐进增强:先实现基础展示,再逐步添加点击、筛选等交互功能

这种"先简单后复杂"的开发方式,可以让我们快速验证功能的价值,再根据用户反馈决定是否继续投入。

下一篇我们来实现召唤师技能列表,展示游戏中所有的召唤师技能。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐