Phần tiếp theo trong series xây dựng game 2D bằng React Native, chúng ta sẽ cùng nhau bắt tay vào xây dựng màn hình Home.
Mình khuyên bạn nên đọc lại phần 1 để biết React Native là gì và chuẩn bị môi trường phát triển trước khi bắt tay vào phần 2 này.
Nội dung chính của bài viết
Cấu trúc lại mã nguồn màn hình Home
Để mã nguồn được clean hơn, mình sẽ tách phần mã JS và CSS ra làm 2 file riêng biệt: index.js và styles.js
Cấu trúc mã nguồn cho mỗi màn hình sẽ đại khái như sau:
# the screens’ directory structure as of now screens ├── Home │ ├── index.js │ └── styles.js └── Routes.js
OK, đầu tiên, chúng ta mở style.js để thêm style cho containner:
import { StyleSheet } from "react-native"; export default StyleSheet.create({ container: { flex: 1, backgroundColor: "#0a0a0a", justifyContent: "center", alignItems: "center" } });
Tiếp theo là import cái style nào vào index.js (Nhớ là phải xóa đoạn code style cũ đi nhé)
Giờ code của index.js sẽ như sau:
// basic imports ... import styles from './styles'; export default class Home extends Component { render() { return ( <View style={styles.container}> {/* // this View is empty on purpose! */} </View> ); } }
💦 Đọc thêm:
- Hướng dẫn sử dụng REACT NATIVE + REDUX + REDUX-SAGA
- [React Native] Tạo tính năng login với Firebase Authentication
- Lập trình React Native – Tâm sự của người mới học React Native
Tạo Header
Cũng giống như mọi ứng dụng khác thôi, chúng ta sẽ cần một Header hiển thị ở mọi màn hình. Với game 2D này, chúng ta sẽ hiển thị một logo như dưới đây:
Để có thể tái sử dụng mã nguồn, chúng ta sẽ tạo một component và đặt tên là Header. Cách làm như sau, bạn tạo mới một file js: Header.js
trong thư mục components và copy đoạn code sau:
import React from "react"; import { Text, View, StyleSheet } from "react-native"; const Header = () => ( <View style={{ flexDirection: "row" }}> <Text style={[styles.header, { color: "#E64C3C" }]}>c</Text> <Text style={[styles.header, { color: "#E57E31" }]}>o</Text> <Text style={[styles.header, { color: "#F1C431" }]}>l</Text> <Text style={[styles.header, { color: "#68CC73" }]}>o</Text> <Text style={[styles.header, { color: "#3998DB" }]}>r</Text> <Text style={styles.header}>blinder</Text> </View> ); const styles = StyleSheet.create({ header: { fontSize: 50, color: "#ecf0f1", fontFamily: "dogbyte" } }); export { Header };
Tiếp theo thì chúng ta export nó vào trong index.js
export * from './Header'
Để sử dụng thì chúng ta cần import nó ở màn hình nào cần tới. Ví dụ ở đây là màn hình Home chẳng hạn:
import { Header } from '../../components' // … <View style={styles.container}> <Header /> </View>
Giờ thì chạy code và kiểm tra thành quả
Nhìn cũng khá ổn rồi đúng không?! Tuy nhiên, nếu tinh mắt thì bạn sẽ phát hiện ra một lỗi nhỏ, và lỗi này chỉ bị trên iOS, đó là thành status bar bị trùng màu với ảnh nền (màu đen), thành ra các chỉ số như đồng hồ, cột sóng bị mất.
Chúng ta sẽ sửa lỗi này trước khi bắt tay vào bước tiếp theo nhé.
Sửa lỗi Status Bar
Chúng ta mở App.js và import component StatusBar từ React-native và Fragment từ React.
import React, { Component, Fragment } from 'react'; import { StatusBar } from 'react-native';
Sau đó thì gọi nó ra:
else { return ( <Fragment> <StatusBar barStyle="light-content" /> <Routes /> </Fragment> ) }
Giờ thì tận hưởng thành quả thôi:
Chỉ với một dòng code nhưng mà trải nghiệm UX tốt hơn hẳn đúng không!
Thêm các thành phần tương tác khác
Như design UX ban đầu của màn hình Home, chúng ta cần một nút Play lớn ở giữa màn hình.
Chúng ta sẽ không sử dụng Button component mặc định của react native ( vì no chứa ảnh và chúng ta cũng không cần background hay border). Thay vào đó, chúng ta sử dụng TouchableOpacity
Đây là component cho phép chúng ta bọc bất kỳ component nào khác bên trong và biến nó trở thành một đối tượng có thể nhận sự kiện touch từ người dùng.
Chúng ta import nó thôi:
import { View, Text, Image, TouchableOpacity } from "react-native";
Tạo sẵn một callback function và hiện tại nó chỉ đơn giản là hiển thị console.log ra thôi:
onPlayPress = () => { console.log("onPlayPress event handler"); };
Sau đó là thêm callback function này vào TouchableOpacity
:
<TouchableOpacity onPress={this.onPlayPress} style={{ flexDirection: 'row', alignItems: 'center' }}> <Image source={require("../../assets/icons/play_arrow.png")} style={styles.playIcon} /> <Text style={styles.play}>PLAY!</Text> </TouchableOpacity>
Tiếp theo là thêm styles trong style.js
play: { fontSize: 45, fontFamily: "dogbyte", color: "#ecf0f1", marginTop: 5 }, playIcon: { height: 60, width: 60, marginRight: 15 }
Ok, giờ thì chạy ứng dụng và kiểm tra thôi
Phần tiếp theo của bài viết, chúng ta sẽ tạo một component hiển thị high score (điểm cao)
Tạo High Score component
Với component này , chúng ta làm tương tự như lúc trước tạo nút play vậy, điểm khác biệt là với component này, chúng ta không phải xử lý sự kiện touch, chỉ đơn giản là hiển thị dữ liệu điểm ra màn hình.
Quay trở lại styles.js
và thêm đoạn style sau:
hiscore: { fontSize: 28.5, fontFamily: "dogbyte", color: "#ecf0f1", marginTop: 5 }, trophyIcon: { height: 45, width: 45, marginRight: 12.5 }
Sau đó thì thêm <Image>
và <Text>
vào trong một <View>
<View style={{ flexDirection: 'row', alignItems: 'center' }}> <Image source={require("../../assets/icons/trophy.png")} style={styles.trophyIcon} /> <Text style={styles.hiscore}>Hi-score: 0</Text> </View>
Hiện tại chúng ta chỉ hard code vậy thôi, sau này khi làm xử lý logic thì chúng ta sẽ hiển thị giá trị điểm thật lên.
Tương tự như hai phần trên, mình sẽ không hướng dẫn chi tiết cụ thể ở đây, bạn tự thực hành nhé, coi như là một bài tập
Trong bản design UX ban đầu, chúng ta có một vài thứ nữa ở phía cuối màn hình như: icon loa để bật tắt âm thanh của game, hay là dòng chữ bản quyền.
Cả hai mục này sẽ cố định ở phía cuối của màn hình nên chúng ta sẽ đưa hết chúng vào một containner
Styles:
bottomContainer: { position: "absolute", left: 15, right: 15, bottom: 12.5 // the 2.5px bottom margin from the text is subtracted from the 15px spacing }, copyrightText: { fontSize: 16, fontFamily: "dogbyte", marginBottom: 2.5 }
Và trong Js:
<View style={styles.bottomContainer}> <Text style={[styles.copyrightText, { color: "#E64C3C" }]}> Music: Komiku </Text> <Text style={[styles.copyrightText, { color: "#F1C431" }]}> SFX: SubspaceAudio </Text> <Text style={[styles.copyrightText, { color: "#3998DB" }]}> Development: RisingStack </Text> </View>
Việc chuyển đổi biểu tượng loa chỉ đơn giản là thay đổi trạng thái state mà thôi, mỗi một giá trị state, mình gắn vào một biểu tượng (còn việc thêm nhạc và hiệu ứng sfx thì mình sẽ thực hiện ở phần sau, ở bài viết sau).
Định nghĩa state cho biểu tượng loa:
state = { isSoundOn: true };
Mỗi lần touch vào biểu tượng loa, chúng ta sẽ chuyển trạng thái tương ứng với hai biếu tượng như dưới đây:
Trong hàm render, chúng ta chuyển trạng thái như sau:
render() { const imageSource = this.state.isSoundOn ? require("../../assets/icons/speaker-on.png") : require("../../assets/icons/speaker-off.png"); // ...
Và phần hiển thị biểu tượng loa ở phía cuối, bên phải màn hình:
<View style={{ flex: 1 }} /> <TouchableOpacity onPress={this.onToggleSound}> <Image source={imageSource} style={styles.soundIcon} /> </TouchableOpacity>
Giờ bạn chạy và kiểm tra thành quả nhé.
Chúng ta tạm kết thúc phần 2 của series tạo game 2D bằng React native tại đây, bạn có thể tải mã nguồn của bài viết về để tham khảo thêm.
Hẹn gặp lại các bạn ở phần tiếp theo: Viết code logic cho game.
Bình luận. Cùng nhau thảo luận nhé!